Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing immutable value types by reference by default

Tags:

c#

value-type

Usally I choose between struct and class not because of memory issues but because of semantics of the type. Some of my value types have quite large memory footprint, sometimes too large to copy this data all the time. So I wonder if it is a good idea to pass immutable value objects always by reference? Since the objects are immutable they cannot by modified by methods that accept them by reference. Are there other issues when passing by reference?

like image 867
hansmaad Avatar asked Apr 10 '12 14:04

hansmaad


3 Answers

Some of my value types have quite large memory footprint

That suggests they shouldn't be value types, from an implementation point of view. From "Design Guidelines for Developing Class Libraries", section "Choosing Between Classes And Structures":

Do not define a structure unless the type has all of the following characteristics:

  • It logically represents a single value, similar to primitive types (integer, double, and so on).
  • It has an instance size smaller than 16 bytes.
  • It is immutable.
  • It will not have to be boxed frequently.

It sounds like you should be creating immutable reference types instead. In many ways they end up "feeling" like value objects anyway (think strings) but you won't need to worry about the efficiency of passing them around.

"Immutability" for value types is a slightly fluid concept - and it certainly doesn't mean that using ref is safe:

// int is immutable, right?
int x = 5;
Foo(ref x);
Console.WriteLine(x); // Eek, prints 6...
...
void Foo(ref int y)
{
    y = 6;
}

We're not changing one part of the value - we're replacing the whole of the value of x with an entirely different value.

Immutability is somewhat easier to think about when it comes to reference types - although even then you can have an object which in itself won't change, but can refer to mutable objects...

like image 66
Jon Skeet Avatar answered Sep 28 '22 03:09

Jon Skeet


Jon's answer is of course correct; I would add this to it: value types are already passed by reference when you call a method on the value type. For example:

struct S
{
    int x;
    public S(int x) { this.x = x; }
    public void M() { Console.WriteLine(this.x); }
}

Method M() is logically the same thing as:

    public static void M(ref S _this) { Console.WriteLine(_this.x); }

Whenever you call an instance method on a struct, we pass a ref to the variable that was the receiver of the call.

So what if the receiver is not a variable? Then the value is copied into a temporary variable which is used as the receiver. And if the value is big, that's potentially an expensive copy!

Value types are copied by value; that's why they're called value types. Unless you are planning on being extremely careful about finding all the possible expensive copies and eliminating them, I would follow the framework design guideline's advice: keep structs under 16 bytes, and pass them by value.

I would also emphasize that Jon is right: passing a struct by ref means passing a reference to a variable, and variables can change. That's why they're called "variables". There is no "const ref" in C# the way there is in C++; even if the value type itself seems to be "immutable" that doesn't mean that the variable holding it is immutable. You can see an extreme example of that in this contrived but educational example:

struct S
{
    readonly int x;
    public S(int x) { this.x = x; }
    public void M(ref S s)
    {
        Console.WriteLine(this.x);
        s = new S(this.x + 1);
        Console.WriteLine(this.x);
    }
}

Is it possible for M to write out two different numbers? You would naively think that the struct is immutable, and therefore x cannot change. But both s and this are variables, and variables can change:

S q = new S(1);
q.M(ref q);

That prints 1, 2 because this and s are both references to q, and nothing is stopping q from changing; it is not readonly.

In short: if I had a lot of data that I wanted to be passing around and have strong guarantees that it was immutable, I'd be using a class, not a struct. Only use a struct in that scenario if you have a demonstrated performance problem that is actually solved by making it a struct, keeping in mind that large structs are potentially very expensive to copy.

like image 33
Eric Lippert Avatar answered Sep 28 '22 02:09

Eric Lippert


So I wonder if it is a good idea to pass immutable value objects always by reference? Since the objects are immutable they cannot by modified by methods that accept them by reference. Are there other issues when passing by reference?

It's not clear exactly what you mean. Assuming that you mean passing it as a ref or out parameter, then the method could merely assign a new instance to the storage location. This would modify what the caller sees because the storage location in the callee is an alias for the storage location passed by the caller.

If you're dealing with memory issues because of copying instances of struct around, you should consider making an immutable reference type, much like string.

like image 41
jason Avatar answered Sep 28 '22 02:09

jason