Why Doesn't C# Support the Return of References

Why doesn't C# support the return of references?

This question was the subject of my blog on June 23rd 2011. Thanks for the great question!

The C# team is considering this for C# 7. See https://github.com/dotnet/roslyn/issues/5233 for details.

UPDATE: The feature made it in to C# 7!


You are correct; .NET does support methods that return managed references to variables. .NET also supports local variables that contain managed references to other variables. (Note however that .NET does not support fields or arrays that contain managed references to other variables because that overly complicates the garbage collection story. Also the "managed reference to variable" types are not convertible to object, and therefore may not be used as type arguments to generic types or methods.)

Commenter "RPM1984" for some reason asked for a citation for this fact. RPM1984 I encourage you to read the CLI specification Partition I Section 8.2.1.1, "Managed pointers and related types" for information about this feature of .NET.

It is entirely possible to create a version of C# which supports both these features. You could then do things like

static ref int Max(ref int x, ref int y) 
{
if (x > y)
return ref x;
else
return ref y;
}

and then call it with

int a = 123;
int b = 456;
ref int c = ref Max(ref a, ref b);
c += 100;
Console.WriteLine(b); // 556!

I know empirically that it is possible to build a version of C# that supports these features because I have done so. Advanced programmers, particularly people porting unmanaged C++ code, often ask us for more C++-like ability to do things with references without having to get out the big hammer of actually using pointers and pinning memory all over the place. By using managed references you get these benefits without paying the cost of screwing up your garbage collection performance.

We have considered this feature, and actually implemented enough of it to show to other internal teams to get their feedback. However at this time based on our research we believe that the feature does not have broad enough appeal or compelling usage cases to make it into a real supported language feature. We have other higher priorities and a limited amount of time and effort available, so we're not going to do this feature any time soon.

Also, doing it properly would require some changes to the CLR. Right now the CLR treats ref-returning methods as legal but unverifiable because we do not have a detector that detects this situation:

ref int M1(ref int x)
{
return ref x;
}

ref int M2()
{
int y = 123;
return ref M1(ref y); // Trouble!
}

int M3()
{
ref int z = ref M2();
return z;
}

M3 returns the contents of M2's local variable, but the lifetime of that variable has ended! It is possible to write a detector that determines uses of ref-returns that clearly do not violate stack safety. What we would do is write such a detector, and if the detector could not prove stack safety, then we would not allow the usage of ref returns in that part of the program. It is not a huge amount of dev work to do so, but it is a lot of burden on the testing teams to make sure that we've really got all the cases. It's just another thing that increases the cost of the feature to the point where right now the benefits do not outweigh the costs.

If you can describe for me why it is you want this feature, I would really appreciate that. The more information we have from real customers about why they want it, the more likely it will make it into the product someday. It's a cute little feature and I'd like to be able to get it to customers somehow if there is sufficient interest.

(See also related questions Is it Possible to Return a Reference to a Variable in C#? and Can I use a reference inside a C# function like C++?)

why return ref a[0] doesn't change the original array?

This happens because q is not a ref local. It is just a regular variable. You are also not saying ref before the method call, so this is just a regular by-value assignment. Because of these reasons, q is not an alias of a[0].

The documentation gives a similar example:

Assume the GetContactInformation method is declared as a ref return:

public ref Person GetContactInformation(string fname, string lname)

A by-value assignment reads the value of a variable and assigns it to a new variable:

Person p = contacts.GetContactInformation("Brandie", "Best");

The preceding assignment declares p as a local variable. Its initial value is copied from reading the value returned by GetContactInformation. Any future assignments to p will not change the value of the variable returned by GetContactInformation. The variable p is no longer an alias to the variable returned.

To fix this, you would add ref before var to make q a ref local, and also add ref before the call to make it a by-ref assignment:

ref var q = ref method(ref s);

Is it Possible to Return a Reference to a Variable in C#?

Update

This feature has been added to C# 7. You can use syntax just like you posted in your question. For example:

double[,,] doubleArray = new double[10,10,10];

ref double GetElement()
{
var (x,y,z) = (1,2,3);
return ref doubleArray[x, y, z];
}

Eric Lippert's answer goes into detail. I would probably delete this answer, but as it's the accepted answer I cannot delete it.

Original Answer

Value types in C# are always passed by value. Objects always have their reference passed by value. This changes in "unsafe" code as Axarydax points out.

The easiest, safest way to avoid this constraint is to make sure that your double is attached to an object somehow.

public class MyObjectWithADouble {
public double Element {get; set;} // property is optional, but preferred.
}

...
var obj = new MyObjectWithADouble();
obj.Element = 5.0

I also want to remark that I'm a little confused about how you anticipate assigning a double to a three-dimensional array. You might want to clarify what you're going for.

I think I understand a little better what you're going for now. You want to return the location of the value in a given array, and then be able to change the value in that location. This pattern breaks some of the expected paradigms of C#, so I would suggest considering other ways to achieve what you're looking for. But if it really makes sense to do it, I'd do something more like this:

public class 3dArrayLocation {public int X; public int Y; public int Z;}

...

public 3dArrayLocation GetElementLocation(...)
{
// calculate x, y, and z
return new 3dArrayLocation {X = x, Y = y, Z = z}
}

...

var location = GetElementLocation(...);
doubleArray[location.X, location.Y, location.Z] = 5.0;

Why doesn't returning by ref work for elements of collections?

The answer is in that same link you posted:

You can only return refs that are “safe to return”: Ones that were
passed to you, and ones that point into fields in objects.

Your example satisfies neither. You are creating the list inside the function (so the object will go out of scope and its pointer will be invalid), and it doesn't point to a field of an object.

Ref return doesn't work when struct is created in the method

The C# documentation states that:

The return value must have a lifetime that extends beyond the execution of the method. In other words, it cannot be a local variable in the method that returns it. It can be an instance or static field of a class, or it can be an argument passed to the method. Attempting to return a local variable generates compiler error CS8168, "Cannot return local 'obj' by reference because it is not a ref local."

In order to use the return ref keyword, you will need to return an object that is not a local variable, as a local variable will go out of scope and be garbage collected. Instead, consider returning a reference to a member variable in your class/struct, as opposed to res. Also, consider whether you need to return the value by reference at all - if you are not accessing it somewhere else internally, there is no need to pass it by reference.


Note, however, that you can use the ref keyword locally to create aliases for local variable names, like so:

Foo a = new Foo();
ref Foo b = ref a;

Here, modifying b will also modify a. With this syntax, though, you can't pass local references outside of the current method scope.

What kinds of objects can return by reference in C#

The key take-away for the rules for managed references (ref) is: a managed reference must not point to a local variable, or to part of one (in the case of a struct), because the reference can outlive the life of the location it points to. It must point to a non-stack location.

Let's take each version one-by-one



        ref int RefReturn()
{
int[] a = { 1, 2 };
return ref a[0];
}

In the above example, the returned reference points to the interior of the array, it does not point to the local variable. The interior of an array is effectively a field of a heap object. The array will outlive the life of the function.



        ref int RefReturn2()
{
Test t = new Test();
return ref t.x;
}

In this one, Test is a reference-type, and therefore lives on the heap. The reference points to the field x of the object contained in t, this also lives on the heap. The fact that t is a local variable is immaterial, the reference does not point to t.



        ref int RefReturnError()
{
int a = 1;
return ref a; //Error
}

In this case, the reference points to the actual location of the local variable, this lives on the stack, and the location will disappear at the end of the function.

Note that the same problem is visible when taking a reference to a field of a struct, when the struct's location is a local variable.

        ref int RefReturnError1A()
{
MyStruct a = new MyStruct();
return ref a.x; //Error
}


        ref Test RefReturnError2()
{
Test t = new Test();
return ref t; //Error
}

In this one, although t is a reference-type and itself points to a heap object, our reference does not point to that object which t points to. It points to the location of t itself which contains that object reference.


Note that a reference to a boxed struct is disallowed for a different reason: due to C#'s unboxing rules, unboxing (logically) creates a copy, therefore you cannot change it in place. Coding in IL directly (or in C++/CLI) you can perfectly verifiably do the equivalent of:

        ref int RefReturnBox()
{
object a = (object)1;
return ref (int)a; // CS0445: Cannot modify the result of an unboxing conversion
}

from C++ to C#: Never Return a Reference to a Local Object?

It is not possible to return references to local variables in .NET

See: Why doesn't C# support the return of references?

See also: Ref returns and ref locals (Eric Lippert's Blog)



Related Topics



Leave a reply



Submit