문제

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?

도움이 되었습니까?

해결책

The original C# code according to the Reference Source is:

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);
} 

The IL code for the method according to ILspy is:

.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

As you can see, there is only one local variable of type StringBuilder.

The variables arg_33_0 and arg_67_0 are an artifact of the decompiler you're using; they're neither in the original C# code nor in the compiled IL code.

다른 팁

I'll say that the two are probably equivalent, because you have to push to the stack the value of this.Key before calling its ToString().

I'll add that in VS 2012 I have created this code:

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();
}

and compiled it in Release mode. Then disassembled it with 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();
}

I'll say identical.

If you want the IL code:

.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

Little differences here...

Theorically the method with the temp variable (Method2) has the initialization of the temp variable (.locals init)...

The other differences are a pop (Method1) vs stloc.0 (Method2) (but both do the same thing, pop a value somewhere, with the difference that pop pops on top of the stack, stloc.0 pops to a named position of the stack), and ldarga.s vs ldloca.s (same thing, only loading an address).

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top