KeyValuePair <tkey,tvalue =“”> .ToString实现细节</tkey,>

时间:2013-08-11 13:48:10

标签: c# .net tostring keyvaluepair

在最近处理其他事情时,我在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());
    }

分配新的局部变量而不是直接使用实例有什么好处?

2 个答案:

答案 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_0arg_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)的初始化...

其他差异是popMethod1)vs stloc.0Method2)(但两者都做同样的事情,在某处弹出一个值,区别在于pop弹出堆栈顶部,stloc.0弹出到堆栈的命名位置),ldarga.s vs ldloca.s(同样,只加载地址)。< / p>