2013-08-11 3 views
1

При работе над чем-то еще в последнее время я столкнулся с немного странным фрагментом кода на KeyValuePair<TKey, TValue>.ToString() implementation.KeyValuePair <TKey, TValue>. Подробности реализации ToString

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

?

В чем преимущества назначения новых локальных переменных вместо непосредственного использования экземпляров?

ответ

4

оригинальный C# код в соответствии с Reference Source является:

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

код IL для способа согласно ILspy является:

.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_0 и arg_67_0 являются артефактом декомпилятора, который вы используете; они не находятся ни в исходном коде C#, ни в компилированном IL-коде.

+0

Подумал об этом 10 лет назад, декомпилируя пользовательскую реализацию 'KeyValuePair', с простым' stringBuilder.Append (Value.Key); '. Большое спасибо! – MarcinJuraszek

+0

Другой вопрос: почему они используют 'StringBuilder' вообще для двух символов и 3 строк? –

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 

Маленькие различия здесь ...

Theorically метод с переменной темп (Method2) имеет инициализацию переменной темп (.locals init) .. .

других различий является pop (Method1) против stloc.0 (Method2) (но оба делают то же самое, поп значение somewher e, с той разницей, что pop всплывает поверх стека, stloc.0 всплывает до именованной позиции стека) и ldarga.s против ldloca.s (то же самое, только загрузка адреса).

Смежные вопросы