Почему я не могу вернуть ссылку на значение словаря?
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.
}
}
В приведенном выше примере GetPropertyValue2 компилируется, а GetPropertyValue и GetPropertyValue3 нет. Что плохого в возврате значения из словаря или списка в качестве ссылки, хотя оно работает для массива?
1 ответ
Я хотел бы добавить свой ответ в "банк", возможно, это делает вещи немного более ясными. Итак, почему это не работает для списков и словарей? Хорошо, если у вас есть такой код:
static string Test()
{
Dictionary<int, string> s = new Dictionary<int, string>();
return s[0];
}
Это (в режиме отладки) переводится в этот код IL:
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
Это, в свою очередь, означает, что вы делаете с одной строкой кода (return s[0]
) на самом деле представляет собой трехэтапный процесс: вызов метода, сохранение возвращаемого значения в локальной переменной, а затем возвращение значения, хранящегося в этой локальной переменной. И, как указано в ссылках, предоставленных другими, возврат локальной переменной по ссылке невозможен (если только локальная переменная не является локальной переменной ref, но, как еще раз указали другие, поскольку Diciotionary<TKey,TValue>
а также List<T>
не имеет API возврата по ссылке, это также невозможно).
А теперь, почему это работает для массива? Если вы посмотрите на то, как индексирование массивов обрабатывается более тщательно (т. Е. На уровне IL-кода), вы увидите, что для индексации массивов нет вызова метода. Вместо этого к коду добавляется специальный код операции, называемый ldelem (или какой-то другой вариант). Код вроде этого:
static string Test()
{
string[] s = new string[2];
return s[0];
}
переводит на это в 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
Конечно, это выглядит так же, как и для словаря, но я думаю, что ключевое отличие состоит в том, что индексатор здесь генерирует собственный вызов IL, а не вызов свойства (то есть метода). И если вы посмотрите здесь на все возможные варианты ldelem в MSDN, то увидите, что есть один, называемый ldelema, который может загружать адрес элемента непосредственно в кучу. И действительно, если вы напишите кусок кода, как это:
static ref string Test()
{
string[] s = new string[2];
return ref s[0];
}
Это приводит к следующему IL-коду, использующему код операции ldelema загрузки с прямой ссылкой:
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
Таким образом, в основном, индексаторы массивов различны, и под капотом массивы поддерживают загрузку элемента по ссылке на стек оценки с помощью собственных вызовов IL. Так как Dictionary<TKey,TValue>
и другие коллекции реализуют индексаторы как свойства, которые приводят к вызовам методов, они могут делать это только в том случае, если вызываемый метод явно указывает возврат ref.