Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ref returns restrictions in C# 7.0

Tags:

c#

c#-7.0

I am trying to understand the following excerpt from an official blog post about new features in C# 7.0 concerned with ref returns.

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

  2. Ref locals are initialized to a certain storage location, and cannot be mutated to point to another.

Unfortunately, the blog post does not give any code example. Would greatly appreciate it if someone could shed more light into the restrictions highlighted in bold with practical examples and an explanation.

Thanks in advance.

like image 936
Ahsan Avatar asked Mar 18 '17 13:03

Ahsan


People also ask

What is ref return?

A reference return value allows a method to return a reference to a variable, rather than a value, back to a caller. The caller can then choose to treat the returned variable as if it were returned by value or by reference.

How do I return a reference in C#?

To return by reference, add the keyword ref before the return type on any method signature and after the keyword return in the method body. For example, the Get method in Score returns the private field value by reference. If value were readonly , the compiler would not permit it to be returned by reference.

What is ref in C?

The ref keyword indicates that a value is passed by reference. It is used in four different contexts: In a method signature and in a method call, to pass an argument to a method by reference. For more information, see Passing an argument by reference. In a method signature, to return a value to the caller by reference.


2 Answers

You've got some answers that clarify the restriction, but not the reasoning behind the restriction.

The reasoning behind the restriction is that we must never allow an alias to a dead variable. If you have an ordinary local in an ordinary method, and you return a ref to it, then the local is dead by the time the ref is used.

Now, one might point out that a local that is returned by ref could be hoisted to a field of a closure class. Yes, that would solve the problem. But the point of the feature is to allow developers to write high-performance close-to-the-machine low-cost mechanisms, and automatically hoisting to a closure -- and then taking on the burdens of collection pressure and so on -- work against that goal.

Things can get slightly tricky. Consider:

ref int X(ref int y) { return ref y; }
ref int Z( )
{
  int z = 123;
  return ref X(ref z);
}

Here we are returning a ref to local z in a sneaky manner! This also has to be illegal. But now consider this:

ref double X(ref int y) { return ref whatever; }
ref double Z( )
{
  int z = 123;
  return ref X(ref z);
}

Now we can know that the returned ref is not the ref to z. So can we say that this is legal if the types of the refs passed in are all different than the types of the refs returned?

What about this?

struct S { public int s; }
ref int X(ref S y) { return ref y.s; }
ref int Z( )
{
  S z = default(S);
  return ref X(ref z);
}

Now once again we have returned a ref to a dead variable.

When we designed this feature for the first time (in 2010 IIRC) there were a number of complicated proposals to deal with these situations, but my favourite proposal was simply "make all of them illegal". You don't get to return a reference that you got returned by a ref-returning method, even if there is no way it could be dead.

I don't know what rule the C# 7 team ended up implementing.

like image 184
Eric Lippert Avatar answered Oct 24 '22 11:10

Eric Lippert


To pass something by reference, it must be classified as variable. C# specification (§5 Variables) define seven categories of variables: static variables, instance variables, array elements, value parameters, reference parameters, output parameters and local variables.

class ClassName {
    public static int StaticField;
    public int InstanceField;
}
void Method(ref int i) { }
void Test1(int valueParameter, ref int referenceParameter, out int outParameter) {
    ClassName instance = new ClassName();
    int[] array = new int[1];
    outParameter=0;
    int localVariable = 0;
    Method(ref ClassName.StaticField);  //Static variable
    Method(ref instance.InstanceField); //Instance variable
    Method(ref array[0]);               //Array element
    Method(ref valueParameter);         //Value parameter
    Method(ref referenceParameter);     //Reference parameter
    Method(ref outParameter);           //Output parameter
    Method(ref localVariable);          //Local variable
}

The first point actually saying that you can ref return variables classified as reference parameters, output parameters, static variables and instance variables.

ref int Test2(int valueParameter, ref int referenceParameter, out int outParameter) {
    ClassName instance = new ClassName();
    int[] array = new int[1];
    outParameter=0;
    int localVariable = 0;
    return ref ClassName.StaticField;  //OK, "ones that point into fields in objects"
    return ref instance.InstanceField; //OK, "ones that point into fields in objects"
    return ref array[0];               //OK, array elements are also "safe to return" by reference
    return ref valueParameter;         //Error
    return ref referenceParameter;     //OK, "ones that were passed to you"
    return ref outParameter;           //OK, "ones that were passed to you"
    return ref localVariable;          //Error
}

Note that for instance fields of value types, you should consider "safe to return" status of enclosing variable. It is not always allowed, as in case for instance fields of reference types:

struct StructName {
    public int InstacneField;
}
ref int Test3() {
    StructName[] array = new StructName[1];
    StructName localVariable = new StructName();
    return ref array[0].InstacneField;      //OK, array[0] is "safe to return"
    return ref localVariable.InstacneField; //Error, localVariable is not "safe to return"
}

Result of ref return method considered "safe to return", if this method does not take any not "safe to return" arguments:

ref int ReturnFirst(ref int i, ref int ignore) => ref i;
ref int Test4() {
    int[] array = new int[1];
    int localVariable = 0;
    return ref ReturnFirst(ref array[0], ref array[0]);      //OK, array[0] is "safe to return"
    return ref ReturnFirst(ref array[0], ref localVariable); //Error, localVariable is not "safe to return"
}

Although we know that ReturnFirst(ref array[0], ref localVariable) will return "safe to return" reference (ref array[0]), compiler can not infer it by analyzing Test4 method in isolation. So, result of ReturnFirst method in that case considered as not "safe to return".

The second point says, that ref local variables declaration must include initializer:

int localVariable = 0;
ref int refLocal1;                     //Error, no initializer
ref int refLocal2 = ref localVariable; //OK

Also, ref local variable can not be reassigned to point to other storage location:

int localVariable1 = 0;
int localVariable2 = 0;
ref int refLocal = ref localVariable1;
ref refLocal = ref localVariable2;     //Error
refLocal = ref localVariable2;         //Error

Actually there is no valid syntax to reassign ref local variable.

like image 44
user4003407 Avatar answered Oct 24 '22 11:10

user4003407