C# 中结构的隐式运算符问题

Implicit operator issue with struct in C#

我正在尝试定义一个助手 class 作为触发器。

行为是当 第一次 测试 true/false 时,它应该 return 为真,并且对于所有后续调用将为假(因此,将用于一次性操作):

public struct Trigger
{
    private bool converted;
    private bool disposed;

    public static implicit operator bool(Trigger trigger)
    {
        if (trigger.disposed)
        {
            return false;
        }

        // Value will only be true once.
        if (!trigger.converted)
        {
            trigger.converted = true;
            trigger.disposed = true;
        }

        return trigger.converted;
    }
}

问题是,隐式运算符 总是 return 为真

从 struct 更改为 class 时它工作正常。

关于定义隐式运算符,我在这里遗漏的结构有什么特别之处吗?

cast-to-bool 运算符强制复制,因此它总是看到一个新的 Trigger 对象。

通过将其设为实例方法来强制它使用相同的方法。 (我是作为 属性 来做的,但它也可以是一种方法)。我也简化了一下。

struct Trigger
{
    private bool triggered;

    public bool IsFresh
    { 
        get
        {
            bool t = this.triggered;
            this.triggered = true;
            return !t;
        }
    }
}   


void Main()
{
    Trigger T = new Trigger();

    var a = T.IsFresh;
    var b = T.IsFresh;
    var c = T.IsFresh;

    Console.WriteLine("{0} {1} {2}", a,b,c);
}

您的问题的评论已经包含答案。

原因是当值类型作为方法(或本例中的"operator")的参数传递时,会生成整个对象的副本。因此,您的字段 converteddisposed 的更新发生在对象的副本上。当控件离开该方法时,不会保留该副本。

如果你制作了一个 ref 参数,那会有所帮助,但在运算符中不允许这样做(有充分的理由),所以它看起来像:

// just to explain, not a recommended "solution"
public static bool ToBool(ref Trigger trigger)  // ByRef parameter
{
    .... // change 'trigger' here and return value
}

当然,实例方法(或实例 属性)也有帮助,请参阅 James Curran 的回答。


如果结构包含引用类型的字段,看看会发生什么是很有启发性的。我选择引用类型 bool[] ,它是一个数组类型。我想要一个可以变异的长度数组。当structTrigger传值时,字段的"value"是对同一个数组实例的引用

// just to explain, not a recommended "solution"
public struct Trigger
{
    bool[] referenceTypeField; // meant to always hold a length-one array of bools

    public static Trigger GetNew()
    {
        return new Trigger { referenceTypeField = new[] { false, }, };
    }

    public static implicit operator bool(Trigger trigger)
    {
        if (trigger.referenceTypeField == null)
            throw new InvalidOperationException("Always construct Trigger through the GetNew method");

        if (trigger.referenceTypeField[0])
            return false;

        trigger.referenceTypeField[0] = true;
        return true;
    }
}

测试:

var t = Trigger.GetNew();
Console.WriteLine(t); // True
Console.WriteLine(t); // False
Console.WriteLine(t); // False

当然我们看到我们真的只是把null问题移到了现场。 default(Trigger) 无效,因为它的字段是 null 引用。


即将推出 C# 的未来版本,其中结构可以具有零参数实例构造函数,并且可以具有实例字段的字段初始值设定项。使用该版本的 C#,您可以:

public struct Trigger
{
    readonly bool[] referenceTypeField = new[] { false, };  // ALLOWED!

    public static implicit operator bool(Trigger trigger)
    {
        if (trigger.referenceTypeField == null)
            throw new InvalidOperationException("Always construct Trigger properly");

        if (trigger.referenceTypeField[0])
            return false;

        trigger.referenceTypeField[0] = true;
        return true;
    }
}

由于像您这样的问题,人们常说可变结构是邪恶的(在 Stack Overflow 上搜索这方面的话题)。最好的建议是放弃使用可变结构的想法(并且不要使用我上面的任何示例代码)。