在最近处理其他事情时,我在KeyValuePair<TKey, TValue>.ToString()
implementation上遇到了一些奇怪的代码。
public override string ToString()
{
StringBuilder stringBuilder = StringBuilderCache.Acquire(16);
stringBuilder.Append('[');
if (this.Key != null)
{
StringBuilder arg_33_0 = stringBuilder;
TKey tKey = this.Key;
arg_33_0.Append(tKey.ToString());
}
stringBuilder.Append(", ");
if (this.Value != null)
{
StringBuilder arg_67_0 = stringBuilder;
TValue tValue = this.Value;
arg_67_0.Append(tValue.ToString());
}
stringBuilder.Append(']');
return StringBuilderCache.GetStringAndRelease(stringBuilder);
}
跳过StringBuilderCache
类用法(这是.NET本身性能改进的一个很好的例子)我有一个问题:
为什么
if (this.Key != null)
{
StringBuilder arg_33_0 = stringBuilder;
TKey tKey = this.Key;
arg_33_0.Append(tKey.ToString());
}
然后
if(this.Key != null)
{
stringBuilder.Append(this.Key.ToString());
}
吗
分配新的局部变量而不是直接使用实例有什么好处?
答案 0 :(得分:4)
根据Reference Source的原始C#代码是:
public override string ToString() {
StringBuilder s = StringBuilderCache.Acquire();
s.Append('[');
if( Key != null) {
s.Append(Key.ToString());
}
s.Append(", ");
if( Value != null) {
s.Append(Value.ToString());
}
s.Append(']');
return StringBuilderCache.GetStringAndRelease(s);
}
根据ILspy的方法的IL代码是:
.method public hidebysig virtual
instance string ToString () cil managed
{
.custom instance void __DynamicallyInvokableAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x5f79c
// Code size 125 (0x7d)
.maxstack 2
.locals init (
[0] class System.Text.StringBuilder,
[1] !TKey,
[2] !TValue
)
IL_0000: ldc.i4.s 16
IL_0002: call class System.Text.StringBuilder System.Text.StringBuilderCache::Acquire(int32)
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldc.i4.s 91
IL_000b: callvirt instance class System.Text.StringBuilder System.Text.StringBuilder::Append(char)
IL_0010: pop
IL_0011: ldarg.0
IL_0012: call instance !0 valuetype System.Collections.Generic.KeyValuePair`2<!TKey, !TValue>::get_Key()
IL_0017: box !TKey
IL_001c: brfalse.s IL_0039
IL_001e: ldloc.0
IL_001f: ldarg.0
IL_0020: call instance !0 valuetype System.Collections.Generic.KeyValuePair`2<!TKey, !TValue>::get_Key()
IL_0025: stloc.1
IL_0026: ldloca.s 1
IL_0028: constrained. !TKey
IL_002e: callvirt instance string System.Object::ToString()
IL_0033: callvirt instance class System.Text.StringBuilder System.Text.StringBuilder::Append(string)
IL_0038: pop
IL_0039: ldloc.0
IL_003a: ldstr ", "
IL_003f: callvirt instance class System.Text.StringBuilder System.Text.StringBuilder::Append(string)
IL_0044: pop
IL_0045: ldarg.0
IL_0046: call instance !1 valuetype System.Collections.Generic.KeyValuePair`2<!TKey, !TValue>::get_Value()
IL_004b: box !TValue
IL_0050: brfalse.s IL_006d
IL_0052: ldloc.0
IL_0053: ldarg.0
IL_0054: call instance !1 valuetype System.Collections.Generic.KeyValuePair`2<!TKey, !TValue>::get_Value()
IL_0059: stloc.2
IL_005a: ldloca.s 2
IL_005c: constrained. !TValue
IL_0062: callvirt instance string System.Object::ToString()
IL_0067: callvirt instance class System.Text.StringBuilder System.Text.StringBuilder::Append(string)
IL_006c: pop
IL_006d: ldloc.0
IL_006e: ldc.i4.s 93
IL_0070: callvirt instance class System.Text.StringBuilder System.Text.StringBuilder::Append(char)
IL_0075: pop
IL_0076: ldloc.0
IL_0077: call string System.Text.StringBuilderCache::GetStringAndRelease(class System.Text.StringBuilder)
IL_007c: ret
} // end of method KeyValuePair`2::ToString
如您所见,只有一个StringBuilder类型的局部变量。
变量arg_33_0
和arg_67_0
是您正在使用的反编译器的工件;它们既不是原始的C#代码,也不是编译的IL代码。
答案 1 :(得分:0)
我会说两者可能是等价的,因为在调用this.Key
之前你必须将ToString()
的值推到堆栈。
我将在VS 2012中添加,我已创建此代码:
public static void Method1<TKey, TValue>(KeyValuePair<TKey, TValue> kvp)
{
TKey key = kvp.Key;
kvp.ToString();
}
public static void Method2<TKey, TValue>(KeyValuePair<TKey, TValue> kvp)
{
kvp.Key.ToString();
}
并在发布模式下编译它。然后用IlSpy拆解它:
public static void Method1<TKey, TValue>(KeyValuePair<TKey, TValue> kvp)
{
TKey arg_07_0 = kvp.Key;
kvp.ToString();
}
public static void Method2<TKey, TValue>(KeyValuePair<TKey, TValue> kvp)
{
TKey key = kvp.Key;
key.ToString();
}
我会说相同。
如果您想要IL代码:
.method public hidebysig static
void Method1<TKey, TValue> (
valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<!!TKey, !!TValue> kvp
) cil managed
{
// Method begins at RVA 0x2cbe
// Code size 23 (0x17)
.maxstack 8
IL_0000: ldarga.s kvp
IL_0002: call instance !0 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<!!TKey, !!TValue>::get_Key()
IL_0007: pop
IL_0008: ldarga.s kvp
IL_000a: constrained. valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<!!TKey, !!TValue>
IL_0010: callvirt instance string [mscorlib]System.Object::ToString()
IL_0015: pop
IL_0016: ret
} // end of method Program::Method1
.method public hidebysig static
void Method2<TKey, TValue> (
valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<!!TKey, !!TValue> kvp
) cil managed
{
// Method begins at RVA 0x2cd8
// Code size 23 (0x17)
.maxstack 1
.locals init (
[0] !!TKey CS$0$0000
)
IL_0000: ldarga.s kvp
IL_0002: call instance !0 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<!!TKey, !!TValue>::get_Key()
IL_0007: stloc.0
IL_0008: ldloca.s CS$0$0000
IL_000a: constrained. !!TKey
IL_0010: callvirt instance string [mscorlib]System.Object::ToString()
IL_0015: pop
IL_0016: ret
} // end of method Program::Method2
这里的差异很小......
理论上,带有temp变量(Method2
)的方法具有temp变量(.locals init
)的初始化...
其他差异是pop
(Method1
)vs stloc.0
(Method2
)(但两者都做同样的事情,在某处弹出一个值,区别在于pop
弹出堆栈顶部,stloc.0
弹出到堆栈的命名位置),ldarga.s
vs ldloca.s
(同样,只加载地址)。< / p>