When shouldn’t you write ref this?

Last time, we saw that the this parameter to an instance method in a struct is passed by reference, allowing the method to re-assign this or pass it as a ref parameter.

Due to limitations in the CLR, the this parameter to an iterator method is not a reference to the caller’s struct, and is instead a copy of the value.  Quoting the spec (§7.6.7)

  • When this is used in a primary-expression within an instance method or instance accessor of a struct, it is classified as a variable. The type of the variable is the instance type (§10.3.1) of the struct within which the usage occurs.
    • If the method or accessor is not an iterator (§10.14), the this variable represents the struct for which the method or accessor was invoked, and behaves exactly the same as a ref parameter of the struct type.
    • If the method or accessor is an iterator, the this variable represents a copy of the struct for which the method or accessor was invoked, and behaves exactly the same as a value parameter of the struct type.

To explain why, you’ll need to understand how iterators are compiled.  As Jon Skeet explains, an iterator method is compiled into a nested class that implements IEnumerable, with the original code transformed into a state machine.  This nested class has fields to store the method’s parameters (which includes this for instance methods) so that the iterator code can use them. 

This is why iterators cannot take ref parameters; the CLR cannot store a reference (as opposed to a reference type) in a field.  Therefore, the this parameter is passed to iterator methods by value, not by reference.
This is also why anonymous methods in structs cannot use this; anonymous methods are also compiled to methods in separate classes and so cannot inherit the ref parameter.

This means that if you change the Mutate method from before into an iterator, the code will still compile (!), but the calling method will not see the changes.

static void Main() {
    Mutable m = new Mutable();
    m.MutateWrong().ToArray();    //Force the iterator to execute
    Console.WriteLine("In Main(): " + m.Value);

struct Mutable {
    public int Value;
    public IEnumerable<int> MutateWrong() {
        this = new Mutable();
        MutateStruct(ref this); 
        Console.WriteLine("Inside MutateWrong(): " + Value);
        yield break;
static void MutateStruct(ref Mutable m) { 
This code prints
Inside MutateWrong(): 1
In Main(): 0
In summary, don’t mutate structs in iterators (or at all, if you can help it).
I don’t know why this isn’t a compiler error.


Post a Comment