1

While working on something else lately I run into a bit strange piece of code on 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);
}

Skipping StringBuilderCache class usage (which is really nice example of performance improvements in .NET itself) I have a question:

Why is

    if (this.Key != null)
    {
        StringBuilder arg_33_0 = stringBuilder;
        TKey tKey = this.Key;
        arg_33_0.Append(tKey.ToString());
    }

better then

    if(this.Key != null)
    {
        stringBuilder.Append(this.Key.ToString());
    }

?

What are the advantages of assigning new local variables instead of using the instances directly?

4

2 回答 2

4

根据参考源的原始 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 代码中。

于 2013-08-11T14:02:14.817 回答
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

这里的细微差别...

从理论上讲,带有临时变量()的方法具有临时变量(Method2)的初始化.locals init......

其他区别是pop( Method1) 与stloc.0( Method2) (但两者都做同样的事情,在某处弹出一个值,不同之处在于pop弹出堆栈顶部,stloc.0弹出堆栈的指定位置)和ldarga.svs ldloca.s(相同的事情,只加载一个地址)。

于 2013-08-11T13:57:01.237 回答