csharplang/proposals/csharp-7.2/conditional-ref.md
Bill Wagner 8d75920537
update 7.2 proposals for markdown lint concerns (#2316)
Primarily these changes:

1. ATX style headers.
1. `csharp` as the language identifier
1. relative link to other published proposals.
2019-03-07 17:03:44 -05:00

2.7 KiB

Conditional ref expressions

The pattern of binding a ref variable to one or another expression conditionally is not currently expressible in C#.

The typical workaround is to introduce a method like:

ref T Choice(bool condition, ref T consequence, ref T alternative)
{
    if (condition)
    {
         return ref consequence;
    }
    else
    {
         return ref alternative;
    }
}

Note that this is not an exact replacement of a ternary since all arguments must be evaluated at the call site.

The following will not work as expected:

       // will crash with NRE because 'arr[0]' will be executed unconditionally
      ref var r = ref Choice(arr != null, ref arr[0], ref otherArr[0]);

The proposed syntax would look like:

     <condition> ? ref <consequence> : ref <alternative>;

The above attempt with "Choice" can be correctly written using ref ternary as:

     ref var r = ref (arr != null ? ref arr[0]: ref otherArr[0]);

The difference from Choice is that consequence and alternative expressions are accessed in a truly conditional manner, so we do not see a crash if arr == null

The ternary ref is just a ternary where both alternative and consequence are refs. It will naturally require that consequence/alternative operands are LValues. It will also require that consequence and alternative have types that are identity convertible to each other.

The type of the expression will be computed similarly to the one for the regular ternary. I.E. in a case if consequence and alternative have identity convertible, but different types, the existing type-merging rules will apply.

Safe-to-return will be assumed conservatively from the conditional operands. If either is unsafe to return the whole thing is unsafe to return.

Ref ternary is an LValue and as such it can be passed/assigned/returned by reference;

     // pass by reference
     foo(ref (arr != null ? ref arr[0]: ref otherArr[0]));

     // return by reference
     return ref (arr != null ? ref arr[0]: ref otherArr[0]);

Being an LValue, it can also be assigned to.

    // assign to
    (arr != null ? ref arr[0]: ref otherArr[0]) = 1;

Ref ternary can be used in a regular (not ref) context as well. Although it would not be common since you could as well just use a regular ternary.

     int x = (arr != null ? ref arr[0]: ref otherArr[0]);

Implementation notes:

The complexity of the implementation would seem to be the size of a moderate-to-large bug fix. - I.E not very expensive. I do not think we need any changes to the syntax or parsing. There is no effect on metadata or interop. The feature is completely expression based. No effect on debugging/PDB either