If type Foo
implements IBar
, there are two ways by which the members of IBar
may be used on an instance of Foo
:
A reference to the instance (for class types) or a reference to a heap object holding a copy of the instance (for value types) may be stored in a variable of type IBar
The instance (for value types), or a reference to it (for class types) may be stored in a variable (or field, array element, or other storage location) of a generic type which is constrained to IBar
.
A storage location of a class type or interface type will hold either a reference to a heap object, or null
. A storage location of a primitive value type will hold a collection of bits representing its value. A storage location of a non-primitive value type will hold the concatenated contents of all its public and private instance fields. A storage location of a generic type T
will hold a heap reference if T
is a class type or interface type, a collection of bits representing a primitive value if T
is a primitive value type, or the concatenated values of a T
's fields if T
is a structure type; this determination is based on the actual type of T
, and not on any of its constraints.
Returning to types Foo
and IBar
, storing a Foo
in an IBar
will cause the system to create a new heap object which will hold the concatenated contents of all the public and private fields of Foo
, and store a reference to that heap object in IBar
. That heap object will then behave like any other heap object, rather than like a value-type storage location. Having things which sometimes behave like value types and sometimes like class types can be confusing; it's best to avoid such mixed semantics when possible.
The main time can be worthwhile for a structure to implement an interface is in cases where usage will follow the second pattern above. For example, one might have a method:
// Return 1, 2, or 3 depending upon how many matching or different things there are
int CountUniqueThings<T>(T first, T second, T third) where T:IEquatable<T>
In that situation, if one were to call CountUniqueThings(4, 6, 6)
, the system could invoke the IEquatable<System.Int32>.Equals(System.Int32)
method directly on the passed-in parameters of type System.Int32
. If the method had instead been declared:
int CountUniqueThings(IEquatable<System.Int32> first,
IEquatable<System.Int32> second,
IEquatable<System.Int32> third)
then the caller would have to create a new heap object to hold the Int32
value 4, a second to hold the value 6, a third which would also hold the value 6, and would then have to pass references to those objects to the CountUniqueThings
method. Icky.
The creation of heap objects has a certain cost. If making something a value type will let one avoid the need to create heap objects to hold the vast majority of instances, that can be a big win. If each instance that's created would necessitate the creation of one heap object to hold a copy, however (e.g. for assignment to an interface-type variable), the value-type advantage would completely evaporate. If instances would on average require the creation of more than one heap object to hold copies (each time a type is converted from the heap type to the value type and back, a new instance will be required), using value types may be far less efficient than simply constructing one class-type instance to which code could pass around a reference.