匿名类型
在编写程序一段时间后,会发现我们要花费很多时间为数据表示创建简单、乏味的类,在数据库应用程序中尤其如此。常常有一系列类只提供属性。本章前面的Curry类就是一个很好的例子:
public class Curry
{
public string MainIngredient { get; set; }
public string Style { get; set; }
public int Spiciness { get; set; }
}
这个类什么也没做,只是存储结构化数据。在数据库或电子表格中,可以把这个类看作表中的一行。可以保存这个类的实例的集合类应表示表或电子表格中的多个行。
这个类完全可以接受的一种用法,但编写这些类的代码比较单调,对底层数据模式的任何修改都需要添加、删除或修改定义类的代码。
匿名类型(anonymous type)是简化这个编程模型的一种方式。其理念是使用C#编译器根据药存储的数据自动创建类型,而不是定义简单的数据存储类型。
可按如下方式实例化前面的Curry类型:
Curry curry = new Curry
{
MainIngredient = "Lamb",
Style = "Dhansak",
Spiciness = 5
};
也可以使用匿名类型,如下所示:
var curry = new
{
MainIngredient = "Lamb",
Style = "Dhansak",
Spiciness = 5
};
这里有两个区别。第一,使用了var关键字。这是因为匿名类型没有可以使用的标识符。稍后可以看到,它们在内部有一个标识符,但不能在代码中使用。第二,在new关键字的后面没有指定类型名称,这是编译器确定我们要使用匿名类型的方式。
IDE检测到匿名类型定义后,会相应地更新IntelliSense。通过前面的声明,可以看到如图14-5所示的匿名类型。
其中,变量curry的类型是'a
。显然,不能在代码中使用这个类型—-它甚至不是合法的标识符名称。'
符号用于在IntelliSense中表示匿名类型。IntelliSense也允许查看匿名类型的成员,如图14-6所示。
注意,这里显示的属性定义为只读属性。这表示,如果要在数据存储对象中修改属性的值,就不能使用匿名类型。
还实现了匿名类型的其他成员,如下面的示例所示。
static void Main(string[] args)
{
var curries = new[]
{
new
{
MainIngredient = "Lamb",
Style = "Dhansak",
Spiciness = 5
},
new
{
MainIngredient = "Lamb",
Style = "Dhansak",
Spiciness = 5
},
new
{
MainIngredient = "Chicken",
Style = "Dhansak",
Spiciness = 5
}
};
Console.WriteLine(curries[0].ToString());
Console.WriteLine(curries[0].GetHashCode());
Console.WriteLine(curries[1].GetHashCode());
Console.WriteLine(curries[2].GetHashCode());
Console.WriteLine(curries[0].Equals(curries[1]));
Console.WriteLine(curries[0].Equals(curries[2]));
Console.WriteLine(curries[0] == curries[1]);
Console.WriteLine(curries[0] == curries[2]);
Console.ReadKey();
}
示例的说明 这个示例创建了一个匿名类型对象的数组,然后使用它测试匿名类型提供的成员。创建匿名类型对象的数组的代码如下:
var curries = new[]
{
new
{
MainIngredient = "Lamb",
Style = "Dhansak",
Spiciness = 5
},
…
};
这段代码通过本节和前面“类型推理”一节中介绍的语法,使用了隐式类型化为匿名类型的数组。结果是curries变量包含3个匿名类型的实例。
创建了这个数组后,代码首先输出在匿名类型上调用ToString()的结果:
Console.WriteLine(curries[0].ToString());
输出结果如下:
{ MainIngredient = Lamb, Style = Dhansak, Spiciness = 5 }
匿名类型上的ToString()的实现输出了为该类型定义的每个属性的值。
接着,代码在数组的3个对象上分别调用GetHashCode():
Console.WriteLine(curries[0].GetHashCode());
Console.WriteLine(curries[1].GetHashCode());
Console.WriteLine(curries[2].GetHashCode());
GetHashCode()执行时,应根据对象的状态为对象返回一个唯一的整数。数组中的前两个对象有相同的属性值,所以其状态是相同的。这些调用的结果是前两个对象的整数相同,第三个对象的整数不同。结果如下:
294897435
294897435
621671265
接着调用Equals()方法比较第一个对象和第二个对象,再比较第一个对象和第三个对象:
Console.WriteLine(curries[0].Equals(curries[1]));
Console.WriteLine(curries[0].Equals(curries[2]));
结果如下:
True
False
匿名类型上的Equals()的实现比较对象的状态,如果一个对象的每个属性值都与另一个对象的对应属性值相同,结果就是true。
但使用==运算符不会得到这样的结果。如前几章所述,==运算符比较对象引用。最后一部分代码执行与上一段代码相同的比较,但用==替代了Equals()方法:
Console.WriteLine(curries[0] == curries[1]);
Console.WriteLine(curries[0] == curries[2]);
curries数组中的每一项都引用匿名类型的不同实例,所以在两种情况下结果都是false。输出结果与预期的相同:
False
False
有趣的是,在创建匿名类型的实例时,编译器会注意到,参数是相同的,所以创建同一匿名类型的3个实例—-而不是3个不同的匿名类型。但是,这并不意味着实例化匿名类型的对象时,编译器会查找匹配的类型。即使在其他地方定义了一个有匹配属性的类,如果使用了匿名类型语法,也只创建(或重用,如本例所示)一个匿名类型。