Value vs reference types when using interfaces in C#
Asked Answered
L

3

9

I would like to create a type in C# with value like semantics. It is immutable and it has a low memory footprint. However, it is mostly going to be accessed via an interface it implements. In this case, a value type would have to be boxed which means that the actual value would have to be copied from the stack to the heap. Therefore I wonder if there is any advantage at all in using a value type (struct) instead of a reference type (class)?

To illustrate my situation, I provide the following example of an interface I with implementations ReferenceTypeImplementation and ValueTypeImplementation:

interface I
{
    int GetSomeInt();
}

class ReferenceTypeImplementation : I
{
    public readonly int i;

    public ReferenceTypeImplementation (int i)
    {
        this.i = i;
    }

    public int GetSomeInt()
    {
        return i*2;
    }
}

struct ValueTypeImplementation : I
{
    public readonly int i;

    public ValueTypeImplementation (int i)
    {
        this.i = i;
    }

    public int GetSomeInt()
    {
        return i*2;
    }
}

I would almost exclusively use these type using the interface I like

I i1 = new ReferenceTypeImplementation(1);
I i2 = new ValueTypeImplementation(1);

Is there any advantage in using ValueTypeImplementation over ReferenceTypeImplementation?

Lavinialavinie answered 30/5, 2013 at 16:59 Comment(12)
As a note, be careful about believing/perpetuating narrow simplications of value types and the stack. blogs.msdn.com/b/ericlippert/archive/2010/09/30/…Hackman
Personally I wouldn't use an interface at all for an immutable type with value semantics. For starters, it's impossible to guarantee to client code that the values in the "immutable" type won't change. (If you need special ways of making instances of the type, you can still use factory classes of course.) But I find that interfaces for simple immutable types with value semantics is just not worthwhile due to the drawbacks.Society
possible duplicate of Structs, Interfaces and BoxingHackman
See also: #3033250Hackman
One benefit to using a value type is that you can pack them more efficiently into large arrays (assuming they are stored as the underlying struct at some layer rather than by interface reference.) I wouldn't consider this a compelling reason, however, unless you've found that memory consumption is a problem and you expect very large arrays.Sollows
@MatthewWatson: While it's possible for class types to implement IEquatable<T>, that interface is most valuable in cases where T is a value type (it has slight value for sealed class types, and should not be implemented by openly-inheritable class types)Cordell
@Cordell I was more concerned about the false immutability.Society
@MatthewWatson: The usefulness of interfaces stems from the fact that, as a matter of practice, classes are generally only coded to implement interfaces when their behavior matches the documented behavior associated with those interfaces. A sorting routine which accepts an IComparer<T> is entitled to assume that if Compare(X,Y) is positive, Compare(Y,X) will be negative, but there's no way the IComparable<T> interface can enforce such a thing.Cordell
@MatthewWatson: If one had interfaces IReadableMatrix<T> and IImmutableMatrix<T>:IReadableMatrix<T> with read-only properties int Height, int Width, and T 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.Cordell
@Cordell Indeed, and IComparable 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!Society
@MatthewWatson: Using a sealed ImmutableMatrix class which encapsulates an array instead of using an IImmutableMatrix 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).Cordell
@Cordell I wouldn't expect a large matrix to have value semantics. I'm thinking more along the lines of ComplexSociety
B
11

Is there any advantage in using ValueTypeImplementation over ReferenceTypeImplementation?

Not if you're going to use it via the interface. As you mentioned, this will box the value type, which negates any potential performance improvement.

In addition, the fact that your expected main usage is going to be via the interface would suggest that you're expecting reference semantics, which again suggests that a class would be more appropriate in this case.

Bornu answered 30/5, 2013 at 17:1 Comment(0)
E
2

Converting a struct to an interface causes boxing. Calling an implicitly implemented member on a struct does not cause boxing:

interface I { void Foo(); }
struct S : I { public void Foo() {} }

S s = new S();
s.Foo(); // No boxing.

I i = s; // Box occurs when casting to interface.
i.Foo();

Here in your case, if you can work with implicit implementation / call then you are better off with struct since your are avoiding;

(a) boxing

(b) no extra memory overhead (which is applicable on any reference type allocation).

Extrinsic answered 30/5, 2013 at 17:7 Comment(0)
C
0

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.

Cordell answered 30/5, 2013 at 20:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.