Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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).

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top