代码改变世界

[易学C#]C#3.0语言新特性之对象和集合初始化器

2009-03-10 21:09  马伟  阅读(1731)  评论(0编辑  收藏  举报
 

C#3.0中,一个对象创建表达式可以包含一个对象或集合初始化器,用于初始化新创建的对象的成员或新创建的集合的元素。

 

对象创建表达式:

new type (argument-list(可选)对象或集合初试化器(可选)

new type 对象或集合初试化器

 

一个对象创建表达式可以省略构造器参数列表,并将其连同圆括号一起替换为一个对象或集合初始化器。省略构造器参数列表并将其连同圆括号一起替换为一个对象或集合初始化器等价于指定一个空的参数列表。

在执行一个带有对象或集合初始化器的对象创建表达式时,首先调用实例构造器,然后执行对象或集合初始化器指定的成员或元素初始化。对象或集合初始化器不能引用正在初始化的对象实例。

20.4.1 引入对象初始器

在传统的C#中,我们常用两种方法来对一个类(或者结构体)进行初始化,一是采用带有参数的构造函数,另一种则是不采用构造函数,或者构造函数中并没有实际地对字段进行赋值,而是在申请了类的实例之后,手动对它的公共属性进行赋值。下面是一个二维空间的几何点例子:

 

public class Point

    {

        private int xPos, yPos;

        //缺省的构造函数

        public Point()

        {

        }

        public Point(int x, int y)

        {

            xPos = x;

            yPos = y;

        }

        public int X

        {

            get { return xPos; }

            set { xPos = value; }

        }

        public int Y

        {

            get { return yPos; }

            set { yPos = value; }

        }

        public override string ToString()

        {

            return string.Format("[{0}, {1}]", xPos, yPos);

        }

    }

 

对于这个类,按照一般方法,我们会这样来初始化它:

 

//调用自定义的构造函数

Point p = new Point(100,200);

 //或者手动指定每个属性

 Point p1 = new Point();

 p1.X = 100;

 p1.Y = 200;

 

现在我们采用类初始化器的C# 3.0代码则可以写成下面的样子:

 

var p1 = new Point { X = 100, Y = 200 };

Point p = new Point { X = 100, Y = 200 };

 

其中第一个是隐式类型变量。这里并没有显式调用Point的构造函数,仅仅是将值设给了公共的XY属性。在这里,类型的缺省构造函数被调用,紧跟着将值赋给指定的属性。从这一点上说,最后这两个实例实际上就是第一个实例的简化写法。

从上面的例子中,我们可以看出:

l         对象初始化器由一系列的成员初始化器构成,包围在{}记号中,并用逗号进行分隔。每个成员初始化器以对象的一个可访问的域或属性的名字开始,后跟一个等号,之后是一个表达式或一个对象或集合初始化器。如果对象初始化其中包括了对同一个域或属性的多于一个的成员初始化器,将会发生错误。

l         在等号后面指定了表达式的成员初始化器的处理与域和属性的赋值一致。

l         在等号后面指定了对象初始化器的成员初始化器也是对一个嵌套对象的初始化。与为域或属性赋一个新值不同,对象初始化器中的赋值被视为对域或属性的成员进行赋值。一个具有值类型的属性不能通过这种构造来进行初始化。

l         在等号后面指定了集合初始化器的成员初始化器也是对一个嵌套集合的初始化。与为域或属性赋一个新的集合不同,初始化器中给定的元素将被添加到域或属性所引用的集合中。该域或属性必须是一个满足下一节所指定的需求的集合类型。

l         对象初时化器是利用了编译器对对象中的对外可见的字段或属性进行按序赋值,在编译还是隐式调用了构造函数,对字段或属性的赋值可以是一个或是多个。

20.4.2 在初始化语法中调用自定义构造函数

在上面的例子中,Point类型初始化时隐式地调用了缺省构造函数。其实我们也被允许直接显式指明使用哪一个构造函数,比如:

 

Point p = new Point() { X = 100, Y = 200 };

 

当然,也可以不调用缺省的构造函数,而是使用自定义的两个参数的那个构造函数,如下所示:

 

Point p = new Point(10, 20) { X = 100, Y = 200 };

 

在上面的代码中,执行的结果是构造函数参数里的10,20被忽略,最终的实例是xPos=100yPos=200。在目前的Point类定义中,调用自定义的构造函数没什么太大的用处,反倒显得累赘。然而,我们给Point结构体新增加一个允许在调用时指定一个颜色(PointColor的枚举类型)的构造函数,那么这样的自定义构造函数与初始化语法之组合就显得很好很强大了。现在,我们来把把Point结构体重构一下,代码如下所示:

 

public enum PointColor

    {

        白色,

        黑色,

        绿色,

        蓝色

    }

    public class Point

    {

        private int xPos, yPos;

        private PointColor c;

        public Point()

        {

        }

        public Point(PointColor color)

        {

            xPos = 0;

            yPos = 0;

            c = color;

        }

        public Point(int x, int y)

        {

            xPos = x;

            yPos = y;

            c = PointColor.绿色;

        }

        public int X

        {

            get { return xPos; }

            set { xPos = value; }

        }

        public int Y

        {

            get { return yPos; }

            set { yPos = value; }

        }

        public override string ToString()

        {

            return string.Format("[{0}, {1}, Color = {2}]", xPos, yPos, c);

        }

    }

 

现在,我们来测试如下代码:

 

class Program

    {

        static void Main()

        {

            Point p = new Point(PointColor.黑色) { X = 100, Y = 200 };

            Point p1 = new Point(10, 20) { X = 100, Y = 200 };

            Console.WriteLine(p);

            Console.WriteLine(p1);

        }

    }

 

最后程序运行的结果为:

 

[100200Color=黑色]

[100200Color=绿色]

 

20.4.3 初始化内部类型

我们来看这样一个实际例子。我们需要定义一个Rectangle类,使用Point类型来代表左上角和右下角两个点的坐标,代码如下所示:

 

public class Rectangle

    {

        private Point topLeft = new Point();

        private Point bottomRight = new Point();

        public Point TopLeft

        {

            get { return topLeft; }

            set { topLeft = value; }

        }

        public Point BottomRight

        {

            get { return bottomRight; }

            set { bottomRight = value; }

        }

        public override string ToString()

        {

            return string.Format("[TopLeft: {0}, {1}, BottomRight: {2}, {3}]",

topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y);

        }

}

 

好了,现在我们就可以使用对象初始化语法,创建一个Rectangle的实例并且将它内部的点设置如下:

 

Rectangle myRect = new Rectangle

    {

        TopLeft = new Point { X = 100, Y = 100 },

        BottomRight = new Point { X = 200, Y = 200 }

    };

 

这样设置是不是简单多了,从而也提高了可读性。这个时候,我们就能够体会到它相对于传统方法的好处了。

在最后,为了能够让读者更好地进行对比学习,我们再给出它的原始调用方法:

 

Rectangle myRect = new Rectangle();

   Point p1 = new Point();

   p1.X = 100;

   p1.Y = 100;

   myRect.TopLeft = p1;

   Point p2 = new Point();

   p2.X = 200;

   p2.Y = 200;

   myRect.BottomRight = p2;

 

20.4.4 集合初始化器

与对象初始化语法类似的是集合初始化。这个语法使得我们可以用简单的数组类型来初始化一个泛型容器(比如List<T>)。可以应用集合初始化器的对象的类型必须实现了System.Collections.Generic.ICollections<T>并指定了确定的T。此外,必须存在从每个元素初始化器的类型到T的隐式转换。如果这些条件不能满足,就会产生一个编译期错误。集合初始化器将依次对每个指定的元素调用ICollection<T>.Add(T)。在这个约束之下,System.Collection命名空间下的容器(比如ArrayList)就不能使用这种新语法,因为它们并没有实现所需的接口。

一个集合初始化器由一系列的元素初始化器构成,包围在{}记号之间,并使用逗号进行分隔。每个元素初始化器指定一个元素,该元素将被添加到待初始化的集合对象中。为了避免与成员初始化器混淆,元素初始化器不能是赋值表达式。如下面的例子所示:

 

// 初始化一个普通数组

 int[] myIntArray = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

 // 初始化一个int型泛型List<>

 List<int> myGenericList = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

 // 下面这句是非法的,ArrayList没有实现ICollection<T>接口

 ArrayList myList = new ArrayList { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

 

现在我们把前面用到的Point类应用进来,比如:

 

static void Main()

 {

    List<Point> myPointList = new List<Point>

    {

        new Point {X = 10, Y = 20},

        new Point {X = 100, Y = 200},

        new Point {X = 1000, Y = 2000}

    };

    foreach (var p in myPointList)

    {

       Console.WriteLine(p);

    }

}

 

程序运行结果如下:

 

[1020Color=白色]

[100200Color=白色]

[10002000Color=白色]