匿名类型

  在编写程序一段时间后,会发现我们要花费很多时间为数据表示创建简单、乏味的类,在数据库应用程序中尤其如此。常常有一系列类只提供属性。本章前面的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个不同的匿名类型。但是,这并不意味着实例化匿名类型的对象时,编译器会查找匹配的类型。即使在其他地方定义了一个有匹配属性的类,如果使用了匿名类型语法,也只创建(或重用,如本例所示)一个匿名类型。

🔚