-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Compile and run the following code:
class C
{
static S M1() => new S() { F = 111 };
static int M2(ref int x, S y)
{
return x;
}
static int M3()
{
return M2(ref M1().Ref(), default);
}
static int M4()
{
return M2(ref M1().Ref(), new S() { F = 123 });
}
static int M5()
{
var x = new S() { F = 124 };
return M2(ref M1().Ref(), x);
}
static void Main()
{
System.Console.WriteLine(M3());
System.Console.WriteLine(M4());
System.Console.WriteLine(M5());
}
}
public struct S
{
public int F;
[System.Diagnostics.CodeAnalysis.UnscopedRef]
public ref int Ref()
{
return ref F;
}
}
Observed:
0
123
111
Expected either an error for Ref invocations, or the following output:
111
111
111
The problem is the fact, that Ref is invoked on a value (vs. a variable) and its result refers to a memory in a short lived temp synthesized by compiler. The lifetime of that temp is the Ref invocation. Compiler is free to reuse it afterwards. Effectively, we end up with a reference to a memory that is no longer alive. This can be observed in IL for M3:
.method private hidebysig static
int32 M3 () cil managed
{
// Method begins at RVA 0x208c
// Code size 28 (0x1c)
.maxstack 2
.locals init (
[0] valuetype S
)
IL_0000: call valuetype S C::M1()
IL_0005: stloc.0
IL_0006: ldloca.s 0
IL_0008: call instance int32& S::Ref()
IL_000d: ldloca.s 0
IL_000f: initobj S
IL_0015: ldloc.0
IL_0016: call int32 C::M2(int32&, valuetype S)
IL_001b: ret
} // end of method C::M3
Note that local 0 is used with different values in the process of calculating both arguments for M2.
Perhaps compiler should disallow invoking UnscopedRef methods on expressions classified as values of struct types. It should be fine to invoke such methods on expressions classified as variables of struct types.