Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# 7 ref return for reference types

I'm going through some code that uses the new features of C# 7 and uses the ref locals & returns feature.

It seems pretty straight forward for value-types where the ref local variable gets a reference (to the actual storage), and updating that updates the value of the original item.

A little explanation will help in understanding how memory references work in case of ref locals for the reference-types. I'm pointing to the last line of the code below:

// A simple class
public class CoolClass
{
    public string Name { get; set; }
    public int Id { get; set; }

    public CoolClass(string name, int id) => (Name, Id) = (name, id);
}

//Dummy method that returns first element with Id > 100
public CoolClass GetSpecialItem_Old(CoolClass[] arr)
{
    for (int i = 0; i < arr.Length; i++)
        if (arr[i].Id > 100)
        {
            return arr[i];
        }
    throw new Exception("Not found");
}

//Same as the other one, but returns by ref C# 7
public ref CoolClass GetSpecialItem_New(CoolClass[] arr)
{
    for (int i = 0; i < arr.Length; i++)
        if (arr[i].Id > 100)
        {
            return ref arr[i];
        }
    throw new Exception("Not found");
}

public void TestMethod()
{
    var arr = new CoolClass[]
    {
        new CoolClass("A", 10),
        new CoolClass("B", 101),
        new CoolClass("C", 11)
    };

    var byVal = GetSpecialItem_Old(arr); //gets back arr[1]
    byVal.Name = "byVal"; //arr[1] = { "byVal", 101 }
    byVal = new CoolClass("newByVal", 25); //arr[1] = { "byVal", 101 }

    ref var byRef = ref GetSpecialItem_New(arr); //gets back arr[1]
    byRef.Name = "byRef"; //arr[1] = { "byRef", 101 }
    //Here it has a different behaviour 
    byRef = new CoolClass("newByRef", 50); //arr[1] = { "newByRef", 50 }
}
like image 910
Arghya C Avatar asked Jan 17 '18 21:01

Arghya C


1 Answers

The fact that the original designers of C# named the feature "ref" was in my opinion a bad idea. It leads one to confuse reference types and "ref" parameters / returns. The better way to think of "ref" is "alias". That is, ref gives you another name for an existing variable.

In your program, byRef is another name for arr[1] regardless of whether arr[1] is of value type or reference type. If arr[1] is a string variable (remember, array elements are variables; you can change them) then byref is a string variable also, and it is the same string variable with a different name.

Note that arr is also a variable; if you change the value of arr, then byRef does not come along for the ride. It remains an alias for the same slot of the same array, regardless of the value of arr.

So when you say

ref var byRef = ref GetSpecialItem_New(arr); //gets back arr[1]

Then

byRef.Name = "byRef"; 

is exactly the same as

arr[1].Name = "byRef";

And when you say

byRef = new CoolClass("newByRef", 50);

That's exactly the same as

arr[1] = new CoolClass("newByRef", 50);

With the caveat that if you changed arr after assigning byRef, you'd still have an alias to the original arr[1].

Again: byRef is simply another way to spell arr[1], such that it always uses the value of arr that it had when byRef was assigned. It's not different for value types or reference types.

In contrast, byVal is NOT an alias for arr[1]. It is a second variable that has a copy of the contents of arr[1]. When you assign to byVal you are not assigning to arr[1]. You're assigning to byVal, which is a different variable.

The contents of arr[1] is a reference, and that reference is copied to byVal, a separate storage location entirely.

like image 106
Eric Lippert Avatar answered Nov 09 '22 10:11

Eric Lippert