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).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.