【问题标题】:KeyValuePair<TKey, TValue>.ToString implementation detailsKeyValuePair<TKey, TValue>.ToString 实现细节
【发布时间】:2013-08-12 21:57:27
【问题描述】:

最近在处理其他事情时,我在KeyValuePair&lt;TKey, TValue&gt;.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);
}

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

?

分配新的局部变量而不是直接使用实例有什么好处?

【问题讨论】:

    标签: c# .net tostring keyvaluepair


    【解决方案1】:

    我会说这两者可能是等价的,因为在调用 ToString() 之前,您必须将 this.Key 的值推入堆栈。

    我将在 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.s vs ldloca.s(同样的事情,只加载一个地址)。

    【讨论】:

      【解决方案2】:

      根据Reference Source的原始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 代码中。

      【讨论】:

      • 在 10 秒前通过反编译自定义 KeyValuePair 实现,使用简单的 stringBuilder.Append(Value.Key); 解决了这个问题。非常感谢!
      • 另一个问题是,为什么他们对两个字符和三个字符串都使用StringBuilder
      猜你喜欢
      • 1970-01-01
      • 2014-02-19
      • 2011-01-16
      • 2019-03-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多