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.
IEquatable<T>
, that interface is most valuable in cases whereT
is a value type (it has slight value for sealed class types, and should not be implemented by openly-inheritable class types) – CordellIComparer<T>
is entitled to assume that ifCompare(X,Y)
is positive,Compare(Y,X)
will be negative, but there's no way theIComparable<T>
interface can enforce such a thing. – CordellIReadableMatrix<T>
andIImmutableMatrix<T>:IReadableMatrix<T>
with read-only propertiesint Height
,int Width
, andT this[int row, int column]
, the documentation for the latter could specify that an instance of the latter must be immutable as long as any reference exists. Note that an instance of a type which implemented e.g.IImmutableMatrix<double>
and reported its dimensions as 10,000 by 10,000 might require vastly less storage (possibly by a factor of over a million) than would an array of such dimensions. – CordellIComparable
must be an interface because it classes need to implement it along with other interfaces - but other classes don't need to be interfaces - especially immutable classes with value semantics - and can therefore be guaranteed to be immutable if you so chose. I guess I lean more towards the ability to be able to mathematically prove the correctness of programs, and you more to ensuring it all works through convention and unit tests. Which is fine of course - we need both! – SocietyImmutableMatrix
class which encapsulates an array instead of using anIImmutableMatrix
interface would prevent outside code from passing in an object which implements the interface in a fashion contrary to its contract, but would also compel the use of a large array to hold the matrix even when other approaches might be literally a million times as efficient (though 1,000,000:1 improvements would be uncommon, 1,000:1 improvements might not be). – CordellComplex
– Society