简体   繁体   中英

Implicit operator issue with struct in C#

I am trying to define a helper class that acts as a trigger.

The behaviour is that when first tested for true/false, it should return true, and for all subsequent calls will be false (thus, will be used for one-time operations):

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;
    }
}

The issue is, that the implicit operator always returns true

When changing from struct to class it works fine.

Is there anything special about structs that i am missing here, with respect to defining implicit operators ?

The cast-to-bool operator is forcing a copy, so it always sees a new Trigger object.

Force it to use the same one by making it an instance method. (I did it as a property, but it could be a method as well). I also simplified it a bit.

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);
}

The comments to your quetion already contain the answer.

The reason is that when a value type is passed as a parameter of a method (or of an "operator" in this case), a copy of the entire object is made . So the update of your fields converted and disposed happens to the copy of the object. That copy is not kept when control leaves the method.

If you made a ref parameter, that would help, but that is not allowed in operators (for good reasons), so it would look like:

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

Of course an instance method (or instance property) could also help, see the answer by James Curran.


It is instructive to see what happens if the struct contains a field of a reference type. I choose the reference type bool[] which is an array type. I want an array of length one which can be mutated. When the struct Trigger is passed by value, the "value" of the field is a reference to the same array instance.

// 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;
    }
}

Test this with:

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

Of course we see that we really just move the null problem to the field. The default(Trigger) is invalid because its field is a null reference.


There is a future version of C# coming up in which structs can have zero-parameter instance constructors, and can have field initializers for instance fields. With that version of C# you could have:

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;
    }
}

It is often said that mutable structs are evil (search for threads on that here on Stack Overflow) because of issues like yours. The best advice is to abandon the idea of having a mutable struct (and not use any of my example code above).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM