【问题标题】:How does dictionary initialization work in C#?字典初始化如何在 C# 中工作?
【发布时间】:2014-01-24 12:52:53
【问题描述】:
var maxDictionary = new Dictionary<int, double> { { 10, 40000 } };

在上面的代码中,编译器是否使用了构造函数?或者编译器是否创建一个 KeyValuePair 并添加到字典中?我试图了解编译器是如何解释它的。

【问题讨论】:

    标签: c# dictionary constructor


    【解决方案1】:

    是的,编译器使用默认的无参数构造函数,然后通过Dictionary.Add 方法添加集合初始化程序中指定的所有值。正如乔恩指出的那样,您的代码被编译成

    Dictionary<int, double> maxDictionary2;
    Dictionary<int, double> maxDictionary;
    
    maxDictionary2 = new Dictionary<int, double>();
    maxDictionary2.Add(10, 40000.0);
    maxDictionary = maxDictionary2;
    

    生成的 IL:

    .maxstack 3
    .locals init (
         [0] class [mscorlib]Dictionary`2<int32, float64> maxDictionary,
         [1] class [mscorlib]Dictionary`2<int32, float64> maxDictionary2)
    L_0000: nop 
    L_0001: newobj instance void [mscorlib]Dictionary`2<int32, float64>::.ctor()
    L_0006: stloc.1 
    L_0007: ldloc.1 
    L_0008: ldc.i4.s 10
    L_000a: ldc.r8 40000
    L_0013: callvirt instance void [mscorlib]Dictionary`2<int32, float64>::Add(!0, !1)
    L_0018: nop 
    L_0019: ldloc.1 
    L_001a: stloc.0 
    

    即创建的字典分配给临时变量maxDictionary2,填充了值,然后才将创建和填充的字典的引用复制到maxDictionary变量。

    请记住,如果您不想使用无参数构造函数,则可以指定任何其他构造函数。例如。你可以使用一个设置初始容量的:

    var maxDictionary = new Dictionary<int, double>(10) { { 10, 40000 } };
    

    【讨论】:

    • 好吧。从逻辑上讲,maxDictionary 的分配发生在 Add 调用之后。如果您实际上是重新分配给现有变量,则这是可见的,其中集合初始化程序可以使用现有变量的值,并且它引用 old 集合。
    • @Uriil 那是因为 DotPeek 是“聪明的”,并试图为您提供惯用的 C# 而不是天真的反编译。就像它会给你一个foreach循环而不是直接调用GetEnumerator()等。
    • @SergeyBerezovskiy:是的 - 赋值运算符的整个右侧逻辑上在赋值运算符本身之前执行。这就是 C# all 的工作方式。在某些情况下,编译器可以将其优化掉——我认为它可能会这样做——但我喜欢考虑集合初始化器的 逻辑 操作。
    • @SergeyBerezovskiy 是的,有,如果你添加一些项目并且一个抛出异常,你不应该得到半生不熟的字典,不是吗?
    • @SriramSakthivel 这也是一个很好的观察。基本上,在分配之前让 RHS 进行评估可以避免很多不直观的行为/陷阱。
    【解决方案2】:
    var maxDictionary = new Dictionary<int, double> { { 10, 40000 } };
    

    这是程序生成的IL

    IL_0001:  newobj      System.Collections.Generic.Dictionary<System.Int32,System.Double>..ctor
    IL_0006:  stloc.1     // <>g__initLocal0
    IL_0007:  ldloc.1     // <>g__initLocal0
    IL_0008:  ldc.i4.s    0A 
    IL_000A:  ldc.r8      00 00 00 00 00 88 E3 40 
    IL_0013:  callvirt    System.Collections.Generic.Dictionary<System.Int32,System.Double>.Add
    IL_0018:  nop         
    IL_0019:  ldloc.1     // <>g__initLocal0
    IL_001A:  stloc.0     // maxDictionary
    

    显然它使用无参数构造函数并调用Add 方法。标签“IL_0013”显示对Add方法的调用

    等效的 c# 代码是

    Dictionary<int, double> maxDictionary;
    Dictionary<int, double> temp = new Dictionary<int, double>();
    temp.Add(10, 40000.0);
    maxDictionary = temp;
    

    值得注意的是编译器使用temp 变量,我可以看到两个原因

    1. 确保在遇到异常时不会得到半生不熟的字典。
    2. 您不希望编译器仅仅为了创建一个新实例和分配而读取该字段。不是吗?

    【讨论】:

      【解决方案3】:

      其他人已经指出 C# 编译器如何发出对DictionaryAdd 方法的调用。但是,仍然没有人谈论何时 C# 编译器发出这些调用。

      嗯,它与System.Collections.Dictionary(或System.Collections.Generic.Dictionary&lt;TKey, TValue&gt;)类无关。此功能称为集合初始化,因为它要求目标实例是实现System.Collections.IEnumerable 接口的类型。唯一的要求是存在一个名为 Add 的方法,甚至从未考虑过它的签名和返回类型。

      Add 方法只需要一个参数时,初始化如下所示:

      class Foo : IEnumerable
      {
          public void Add(string a)
          {
      
          }
      
          public IEnumerator GetEnumerator()
          {
              throw new NotImplementedException();
          }
      }
      
      var myFoo = new Foo
      {
          "item 1",
          "item 2",
          "item 3",
          "item 4",
          "item 5"
      };
      

      上面的代码正好代表System.Collections.List(或System.Collections.Generic.List&lt;T&gt;)类发生了什么,因为它实现了System.Collections.IEnumerable

      Add 方法需要两个或更多参数时,就像字典一样,那么每个项目都必须具有该方法的每个参数的值,因此它被设计为将这些值包含在大括号中,如下所示:

      class Foo : IEnumerable
      {
          public void Add(string a, int b, bool c)
          {
      
          }
      
          public IEnumerator GetEnumerator()
          {
              throw new NotImplementedException();
          }
      }
      
      var myFoo = new Foo
      {
          { "item 1", 3, false },
          { "item 2", 1, true },
          { "item 3", 4, true },
          { "item 4", 1, true },
          { "item 5", 5, true },
      };
      
      对于好奇的人:

      这个答案中的代码包含一个与圈子相关的有趣的东西。会有人发现它吗?

      【讨论】:

        猜你喜欢
        • 2011-02-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-27
        相关资源
        最近更新 更多