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.
}
}
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,GetPropertyValue2编译,但GetPropertyValue和GetPropertyValue3没有.从字典或列表中返回值作为引用有什么问题,而它对数组有效吗?
我想将我的答案添加到“锅”中,也许这会让事情变得更清楚一些。那么,为什么这不适用于列表和字典呢?好吧,如果你有一段这样的代码:
static string Test()
{
Dictionary<int, string> s = new Dictionary<int, string>();
return s[0];
}
Run Code Online (Sandbox Code Playgroud)
这(在调试模式下)转换为以下 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
Run Code Online (Sandbox Code Playgroud)
反过来,这意味着您使用一行代码 ( return s[0]) 执行的操作实际上是一个三步过程:调用方法、将返回值存储在局部变量中,然后返回存储在该局部变量中的值。而且,正如其他人提供的链接所指出的,通过引用返回局部变量是不可能的(除非局部变量是 ref 局部变量,但正如其他人再次指出的那样,因为 和Diciotionary<TKey,TValue>没有List<T>by-参考返回API,这也是不可能的)。
现在,为什么它适用于数组?如果您仔细观察数组索引的处理方式(即在 IL 代码级别),您会发现没有用于数组索引的方法调用。相反,一个特殊的操作码被添加到名为 ldelem(或其某些变体)的代码中。像这样的代码:
static string Test()
{
string[] s = new string[2];
return s[0];
}
Run Code Online (Sandbox Code Playgroud)
在 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
Run Code Online (Sandbox Code Playgroud)
当然,这看起来与字典相同,但我认为关键区别在于这里的索引器生成 IL 本地调用,而不是属性(即方法)调用。如果你在 MSDN上查看所有可能的 ldelem 变体,你会发现有一个名为ldelema的变体可以将元素的地址直接加载到堆中。事实上,如果你写一段这样的代码:
static ref string Test()
{
string[] s = new string[2];
return ref s[0];
}
Run Code Online (Sandbox Code Playgroud)
利用直接引用加载 ldelema 操作码,这会转换为以下 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: ldelema [mscorlib]System.String
IL_000f: stloc.1
IL_0010: br.s IL_0012
IL_0012: ldloc.1
IL_0013: ret
Run Code Online (Sandbox Code Playgroud)
因此,基本上,数组索引器是不同的,并且在底层,数组支持通过本机 IL 调用引用计算堆栈来加载元素。由于Dictionary<TKey,TValue>和其他集合将索引器实现为属性,这会导致方法调用,因此只有在调用的方法显式指定 ref 返回时,它们才能执行此操作。
| 归档时间: |
|
| 查看次数: |
265 次 |
| 最近记录: |