为什么我不能返回对字典值的引用?

时间:2018-01-03 10:39:45

标签: ref c#-7.0

let decodedString = "www.mydomain.com%2Fkey=%E0%A4%85%E0%A4%95%E0%A5%8D%E0%A4%B7%E0%A4%AF"

if let unwrappedDecodedString = decodedString.removingPercentEncoding {
    print(unwrappedDecodedString) // www.mydomain.com/key=अक्षय
}

在上面的示例中,GetPropertyValue2编译,但GetPropertyValue和GetPropertyValue3没有。 将字典或列表中的值作为引用返回有什么问题,而它对数组有效吗?

1 个答案:

答案 0 :(得分:2)

我想将我的答案添加到“锅”中,也许这会让事情变得更加清晰。 那么,为什么列表和词典不起作用呢?好吧,如果你有一段这样的代码:

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>和{ {1}}没有按引用返回API,这也是不可能的。

现在,为什么它适用于阵列?如果你看一下如何更仔细地处理数组索引(即在IL代码级别),你可以看到没有方法调用数组索引。相反,一个特殊的操作码被添加到名为ldelem(或其中的一些变体)的代码中。像这样的代码:

List<T>

在IL中翻译成这个:

 static string Test()
 {
    string[] s = new string[2];
    return s[0];
 }

当然这看起来和字典一样,但我认为关键的区别在于索引器在这里生成一个IL-native调用,而不是一个属性(即方法)调用。如果你查看MSDN here上所有可能的ldelem变体,你可以看到有一个叫做 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: ldelem.ref
IL_000b: stloc.1
IL_000c: br.s IL_000e

IL_000e: ldloc.1
IL_000f: ret

这转换为以下IL代码,利用直接引用加载ldelema操作码:

static ref string Test()
{
   string[] s = new string[2];
   return ref 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: ldelema [mscorlib]System.String IL_000f: stloc.1 IL_0010: br.s IL_0012 IL_0012: ldloc.1 IL_0013: ret 和其他集合将索引器实现为属性,这导致方法调用,因此只有在显式调用的方法指定ref返回时,它们才能执行此操作。