为什么我不能 return 对字典值的引用?
Why can I not return a reference to a dictionary value?
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 不能。
从字典或列表中返回一个值作为引用有什么问题,而它确实适用于数组?
我想将我的答案添加到 'pot',也许这会让事情更清楚一些。
那么,为什么这不适用于列表和字典呢?好吧,如果你有一段这样的代码:
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]
)所做的实际上是一个三步过程:调用方法,将return值存储在局部变量中然后 returning 存储在该局部变量中的值。并且,正如其他人提供的链接所指出的那样,return 引用局部变量是不可能的(除非局部变量是 ref 局部变量,但正如其他人再次指出的那样,因为 Diciotionary<TKey,TValue>
和 List<T>
没有引用 return 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-native 调用,而不是 属性(即方法)调用。如果你在 MSDN here 上查看所有可能的 ldelem 变体,你会发现有一种叫做 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 returns.
时执行此操作
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 不能。 从字典或列表中返回一个值作为引用有什么问题,而它确实适用于数组?
我想将我的答案添加到 'pot',也许这会让事情更清楚一些。 那么,为什么这不适用于列表和字典呢?好吧,如果你有一段这样的代码:
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]
)所做的实际上是一个三步过程:调用方法,将return值存储在局部变量中然后 returning 存储在该局部变量中的值。并且,正如其他人提供的链接所指出的那样,return 引用局部变量是不可能的(除非局部变量是 ref 局部变量,但正如其他人再次指出的那样,因为 Diciotionary<TKey,TValue>
和 List<T>
没有引用 return 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-native 调用,而不是 属性(即方法)调用。如果你在 MSDN here 上查看所有可能的 ldelem 变体,你会发现有一种叫做 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 returns.