public class PropertyManager
{
private Dictionary<ElementPropertyKey, string> _values = new Dictionary<ElementPropertyKey, string>();
private string[] _values2 = new string[1];
private List<string> _values3 = new List<string>();
public PropertyManager()
{
_values[new ElementPropertyKey(5, 10, "Property1")] = "Value1";
_values2[0] = "Value2";
_values3.Add("Value3");
}
public ref string GetPropertyValue(ElementPropertyKey key)
{
return ref _values[key]; //Does not compile. Error: An expression cannot be used in this context because it may not be returned by reference.
}
public ref string GetPropertyValue2(ElementPropertyKey key)
{
return ref _values2[0]; //Compiles
}
public ref string GetPropertyValue3(ElementPropertyKey key)
{
return ref _values3[0]; //Does not compile. Error: An expression cannot be used in this context because it may not be returned by reference.
}
}
In the above example GetPropertyValue2 compiles, but GetPropertyValue and GetPropertyValue3 do not. What is wrong with returning a value from a dictionary or list as reference, while it does work for an array?
I'd like to add my answer to the 'pot', maybe it makes things a bit more clear. So, why doesn't that work for lists and dictionaries? Well, if you have a piece of code like this:
static string Test()
{
Dictionary<int, string> s = new Dictionary<int, string>();
return s[0];
}
This (in debug mode) translates to this IL code:
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<int32, string>::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldc.i4.0
IL_0009: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int32, string>::get_Item(!0)
IL_000e: stloc.1
IL_000f: br.s IL_0011
IL_0011: ldloc.1
IL_0012: ret
This, in turn means that what you do with one line of code (return s[0]
) is actually a three-step process: calling the method, storing the return value in a local variable and then returning the value that is stored in that local variable. And, as pointed out by the links the others have provided, returning a local variable by reference is not possible (unless the local variable is a ref local variable, but as pointed out by the others again, since Diciotionary<TKey,TValue>
and List<T>
does not have a by-reference return API, this is not possible either).
And now, why does it work for the array? If you look at how array-indexing is handled more closely (i.e. on IL-code level), you can see that there is no method call for array indexing. Instead, a special opcode is added to the code called ldelem (or some variant of that). A code like this:
static string Test()
{
string[] s = new string[2];
return s[0];
}
translates to this in IL:
IL_0000: nop
IL_0001: ldc.i4.2
IL_0002: newarr [mscorlib]System.String
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldc.i4.0
IL_000a: ldelem.ref
IL_000b: stloc.1
IL_000c: br.s IL_000e
IL_000e: ldloc.1
IL_000f: ret
Of course this looks like the same as it was for the dictionary, but I think the key difference is that the indexer here generates an IL-native call, not a property (i.e. method) call. And if you look at all the possible ldelem variants on MSDN here, you can see that there is one called ldelema which can load the address of the element directly to the heap. And indeed, if you write a piece of code like this:
static ref string Test()
{
string[] s = new string[2];
return ref s[0];
}
This translates to the following IL code, utilizing the direct-reference loading ldelema opcode:
IL_0000: nop
IL_0001: ldc.i4.2
IL_0002: newarr [mscorlib]System.String
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldc.i4.0
IL_000a: ldelema [mscorlib]System.String
IL_000f: stloc.1
IL_0010: br.s IL_0012
IL_0012: ldloc.1
IL_0013: ret
So basically, array indexers are different and under the hood, arrays have support for loading an element by reference to the evaluation stack via native IL calls. Since the Dictionary<TKey,TValue>
and other collections implement indexers as properties, which result in method calls, they can only do this if the method called explicitly specifies ref returns.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With