在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的構造函數,僅僅是 將值設給了公共的X和Y屬性。在這裡,類型的缺省構造函數被調用,緊跟著將值 賦給指定的屬性。從這一點上說,最後這兩個實例實際上就是第一個實例的簡化 寫法。
從上面的例子中,我們可以看出:
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=100,yPos=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);
}
}
最後程序運行的結果為:
[100,200,Color=黑色]
[100,200,Color=綠色]
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);
}
}
程序運行結果如下:
[10,20,Color=白色]
[100,200,Color=白色]
[1000,2000,Color=白色]