Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top