Is it safe for structs to implement interfaces?

前端 未结 9 2067
既然无缘
既然无缘 2020-11-29 16:44

I seem to remember reading something about how it is bad for structs to implement interfaces in CLR via C#, but I can\'t seem to find anything about it. Is it bad? Are the

相关标签:
9条回答
  • 2020-11-29 17:11

    In some cases it may be good for a struct to implement an interface (if it was never useful, it's doubtful the creators of .net would have provided for it). If a struct implements a read-only interface like IEquatable<T>, storing the struct in a storage location (variable, parameter, array element, etc.) of type IEquatable<T> will require that it be boxed (each struct type actually defines two kinds of things: a storage location type which behaves as a value type and a heap-object type which behaves as a class type; the first is implicitly convertible to the second--"boxing"--and the second may be converted to the first via explicit cast--"unboxing"). It is possible to exploit a structure's implementation of an interface without boxing, however, using what are called constrained generics.

    For example, if one had a method CompareTwoThings<T>(T thing1, T thing2) where T:IComparable<T>, such a method could call thing1.Compare(thing2) without having to box thing1 or thing2. If thing1 happens to be, e.g., an Int32, the run-time will know that when it generates the code for CompareTwoThings<Int32>(Int32 thing1, Int32 thing2). Since it will know the exact type of both the thing hosting the method and the thing that's being passed as a parameter, it won't have to box either of them.

    The biggest problem with structs that implement interfaces is that a struct which gets stored in a location of interface type, Object, or ValueType (as opposed to a location of its own type) will behave as a class object. For read-only interfaces this is not generally a problem, but for a mutating interface like IEnumerator<T> it can yield some strange semantics.

    Consider, for example, the following code:

    List<String> myList = [list containing a bunch of strings]
    var enumerator1 = myList.GetEnumerator();  // Struct of type List<String>.IEnumerator
    enumerator1.MoveNext(); // 1
    var enumerator2 = enumerator1;
    enumerator2.MoveNext(); // 2
    IEnumerator<string> enumerator3 = enumerator2;
    enumerator3.MoveNext(); // 3
    IEnumerator<string> enumerator4 = enumerator3;
    enumerator4.MoveNext(); // 4
    

    Marked statement #1 will prime enumerator1 to read the first element. The state of that enumerator will be copied to enumerator2. Marked statement #2 will advance that copy to read the second element, but will not affect enumerator1. The state of that second enumerator will then be copied to enumerator3, which will be advanced by marked statement #3. Then, because enumerator3 and enumerator4 are both reference types, a REFERENCE to enumerator3 will then be copied to enumerator4, so marked statement will effectively advance both enumerator3 and enumerator4.

    Some people try to pretend that value types and reference types are both kinds of Object, but that's not really true. Real value types are convertible to Object, but are not instances of it. An instance of List<String>.Enumerator which is stored in a location of that type is a value-type and behaves as a value type; copying it to a location of type IEnumerator<String> will convert it to a reference type, and it will behave as a reference type. The latter is a kind of Object, but the former is not.

    BTW, a couple more notes: (1) In general, mutable class types should have their Equals methods test reference equality, but there is no decent way for a boxed struct to do so; (2) despite its name, ValueType is a class type, not a value type; all types derived from System.Enum are value types, as are all types which derive from ValueType with the exception of System.Enum, but both ValueType and System.Enum are class types.

    0 讨论(0)
  • 2020-11-29 17:18

    There is very little reason for a value type to implement an interface. Since you cannot subclass a value type, you can always refer to it as its concrete type.

    Unless of course, you have multiple structs all implementing the same interface, it might be marginally useful then, but at that point I'd recommend using a class and doing it right.

    Of course, by implementing an interface, you are boxing the struct, so it now sits on the heap, and you won't be able to pass it by value anymore...This really reinforces my opinion that you should just use a class in this situation.

    0 讨论(0)
  • 2020-11-29 17:19

    There are no consequences to a struct implementing an interface. For example the built-in system structs implement interfaces like IComparable and IFormattable.

    0 讨论(0)
提交回复
热议问题