匿名类型

  在编写程序一段时间后,会发现我们要花费很多时间为数据表示创建简单、乏味的类,在数据库应用程序中尤其如此。常常有一系列类只提供属性。本章前面的Curry类就是一个很好的例子:

  1. public class Curry
  2. {
  3. public string MainIngredient { get; set; }
  4. public string Style { get; set; }
  5. public int Spiciness { get; set; }
  6. }

  这个类什么也没做,只是存储结构化数据。在数据库或电子表格中,可以把这个类看作表中的一行。可以保存这个类的实例的集合类应表示表或电子表格中的多个行。

  这个类完全可以接受的一种用法,但编写这些类的代码比较单调,对底层数据模式的任何修改都需要添加、删除或修改定义类的代码。

  匿名类型(anonymous type)是简化这个编程模型的一种方式。其理念是使用C#编译器根据药存储的数据自动创建类型,而不是定义简单的数据存储类型。

  可按如下方式实例化前面的Curry类型:

  1. Curry curry = new Curry
  2. {
  3. MainIngredient = "Lamb",
  4. Style = "Dhansak",
  5. Spiciness = 5
  6. };

  也可以使用匿名类型,如下所示:

  1. var curry = new
  2. {
  3. MainIngredient = "Lamb",
  4. Style = "Dhansak",
  5. Spiciness = 5
  6. };

这里有两个区别。第一,使用了var关键字。这是因为匿名类型没有可以使用的标识符。稍后可以看到,它们在内部有一个标识符,但不能在代码中使用。第二,在new关键字的后面没有指定类型名称,这是编译器确定我们要使用匿名类型的方式。

  IDE检测到匿名类型定义后,会相应地更新IntelliSense。通过前面的声明,可以看到如图14-5所示的匿名类型。

 14.3 匿名类型  - 图1

  其中,变量curry的类型是'a。显然,不能在代码中使用这个类型—-它甚至不是合法的标识符名称。'符号用于在IntelliSense中表示匿名类型。IntelliSense也允许查看匿名类型的成员,如图14-6所示。

 14.3 匿名类型  - 图2

  注意,这里显示的属性定义为只读属性。这表示,如果要在数据存储对象中修改属性的值,就不能使用匿名类型。

  还实现了匿名类型的其他成员,如下面的示例所示。

  1. static void Main(string[] args)
  2. {
  3. var curries = new[]
  4. {
  5. new
  6. {
  7. MainIngredient = "Lamb",
  8. Style = "Dhansak",
  9. Spiciness = 5
  10. },
  11. new
  12. {
  13. MainIngredient = "Lamb",
  14. Style = "Dhansak",
  15. Spiciness = 5
  16. },
  17. new
  18. {
  19. MainIngredient = "Chicken",
  20. Style = "Dhansak",
  21. Spiciness = 5
  22. }
  23. };
  24. Console.WriteLine(curries[0].ToString());
  25. Console.WriteLine(curries[0].GetHashCode());
  26. Console.WriteLine(curries[1].GetHashCode());
  27. Console.WriteLine(curries[2].GetHashCode());
  28. Console.WriteLine(curries[0].Equals(curries[1]));
  29. Console.WriteLine(curries[0].Equals(curries[2]));
  30. Console.WriteLine(curries[0] == curries[1]);
  31. Console.WriteLine(curries[0] == curries[2]);
  32. Console.ReadKey();
  33. }
  示例的说明  这个示例创建了一个匿名类型对象的数组,然后使用它测试匿名类型提供的成员。创建匿名类型对象的数组的代码如下:

  1. var curries = new[]
    {
    new
    {
    MainIngredient = "Lamb",
    Style = "Dhansak",
    Spiciness = 5
    },

    };


  这段代码通过本节和前面“类型推理”一节中介绍的语法,使用了隐式类型化为匿名类型的数组。结果是curries变量包含3个匿名类型的实例。

  创建了这个数组后,代码首先输出在匿名类型上调用ToString()的结果:

  1. Console.WriteLine(curries[0].ToString());

  输出结果如下:

  1. { MainIngredient = Lamb, Style = Dhansak, Spiciness = 5 }

  匿名类型上的ToString()的实现输出了为该类型定义的每个属性的值。

  接着,代码在数组的3个对象上分别调用GetHashCode():

  1. Console.WriteLine(curries[0].GetHashCode());
  2. Console.WriteLine(curries[1].GetHashCode());
  3. Console.WriteLine(curries[2].GetHashCode());

  GetHashCode()执行时,应根据对象的状态为对象返回一个唯一的整数。数组中的前两个对象有相同的属性值,所以其状态是相同的。这些调用的结果是前两个对象的整数相同,第三个对象的整数不同。结果如下:

  1. 294897435
  2. 294897435
  3. 621671265

  接着调用Equals()方法比较第一个对象和第二个对象,再比较第一个对象和第三个对象:

  1. Console.WriteLine(curries[0].Equals(curries[1]));
  2. Console.WriteLine(curries[0].Equals(curries[2]));

  结果如下:

  1. True
  2. False

  匿名类型上的Equals()的实现比较对象的状态,如果一个对象的每个属性值都与另一个对象的对应属性值相同,结果就是true。

  但使用==运算符不会得到这样的结果。如前几章所述,==运算符比较对象引用。最后一部分代码执行与上一段代码相同的比较,但用==替代了Equals()方法:

  1. Console.WriteLine(curries[0] == curries[1]);
  2. Console.WriteLine(curries[0] == curries[2]);

  curries数组中的每一项都引用匿名类型的不同实例,所以在两种情况下结果都是false。输出结果与预期的相同:

  1. False
  2. False

  有趣的是,在创建匿名类型的实例时,编译器会注意到,参数是相同的,所以创建同一匿名类型的3个实例—-而不是3个不同的匿名类型。但是,这并不意味着实例化匿名类型的对象时,编译器会查找匹配的类型。即使在其他地方定义了一个有匹配属性的类,如果使用了匿名类型语法,也只创建(或重用,如本例所示)一个匿名类型。