微软免费图书《Introducing Microsoft LINQ》翻译Chapter2.1:C# 3.0 特性(对象初始化表达式\匿名类型\查询表达式)...
本書翻譯目的為個人學(xué)習(xí)和知識共享,其版權(quán)屬原作者所有,如有侵權(quán),請告知本人,本人將立即對發(fā)帖采取處理。
允許轉(zhuǎn)載,但轉(zhuǎn)載時請注明本版權(quán)聲明信息,禁止用于商業(yè)用途!
博客園:韓現(xiàn)龍
Introducing to Microsoft LINQ目錄
對象初始化表達(dá)式(Object Initialization Expressions)
C#1.x允許我們在單獨的聲明上對字段或者本地的變量進(jìn)行初始化。這兒展示的語法可以初始化一個單獨的標(biāo)識符:
int?i?=?3;string?name?=?'Unknown';
Customer?c?=?new?Customer(?"Tom",?32?);
當(dāng)這種初始化表達(dá)式用于引用類型時,它需要調(diào)用類的構(gòu)造函數(shù),構(gòu)造函數(shù)可能含有標(biāo)識如何對該類進(jìn)行實例化的參數(shù)。你可以對引用類型和值類型都使用對象初始化器。
當(dāng)你想初始化一個對象(無論是引用類型還是值類型)時,你需要有含有足夠參數(shù)的構(gòu)造函數(shù)來指明該對象的初始狀態(tài)如何被初始化:思考如下代碼:
?1public?class?Customer?{?2????public?int?Age;
?3????public?string?Name;
?4????public?string?Country;
?5????public?Customer(?string?name,?int?age?)?{
?6????????this.Name?=?name;
?7????????this.Age?=?age;
?8????}
?9????//?…
10}
11
Customer實例是通過Customer類的構(gòu)造函數(shù)初始化的,但是我們僅設(shè)定了它的Name和Age字段。如果我們想設(shè)定Country而并且Age,我們要寫如Listing 2-27這樣的代碼:
Listing 2-27: Standard syntax for object initialization??
Customer?customer?=?new?Customer();customer.Name?=?"Marco";
customer.Country?=?"Italy";
C#3.0為對象初始化語法引入了更簡潔的模式,如Listing 2-28:
Listing 2-28: Object initializer
//?Implicitly?calls?default?constructor?before?object?initializationCustomer?customer?=?new?Customer?{?Name?=?"Marco",?Country?=?"Italy"?};
小貼士 用來初始化對象(標(biāo)準(zhǔn)的對象初始化器)的語法在代碼編譯后是相同的。對象初始化器產(chǎn)生了一個為特定類型進(jìn)行的構(gòu)造函數(shù)的調(diào)用(無論是引用類型還是值類型):無論何時在類型名稱和開放括號之前你沒有用括號閉合時它都是默認(rèn)的構(gòu)造函數(shù)。如果該構(gòu)造函數(shù)對成員字段成功地進(jìn)行了初始化,編譯器還是會做那個工作,即使這些聲明可能沒有被用到。如果被初始化類型的構(gòu)造函數(shù)是空的話,對象初始化器是不會有額外的花銷的。
這些在初始化列表中指定的名字和被初始化對象的公用的字段或者屬性有關(guān)。若默認(rèn)的構(gòu)造函數(shù)對一個類型不可用的話,語法也允許對非默認(rèn)構(gòu)造函數(shù)進(jìn)行調(diào)用。在Listing2-29中展示了這個例子:
Listing 2-29: Explicit constructor call in object initializer
1//?Explicitly?specify?constructor?to?call?before?object?initialization2Customer?c1?=?new?Customer()?{?Name?=?"Marco",?Country?=?"Italy"?};
3?
4//?Explicitly?specify?nondefault?constructor
5Customer?c2?=?new?Customer(?"Paolo",?21?)?{?Country?=?"Italy"?};
6
The c2 assignment above is equivalent to this one:
c2的聲明和下面的代碼是一樣的:
?
Customer?c2?=?new?Customer(?"Paolo",?21?);?c2.Country?=?"Italy";
????小貼士 對象初始化器的實現(xiàn)其實是創(chuàng)建并初始化對象為一個臨時的變量,并且僅在最后才將該對象的引用拷貝到目標(biāo)變量。通過這種方法,在對象完全被初始化之前對另外一個線程是不可見的。
這種對象初始化器的好處之一是它允許你以函數(shù)的形式寫出一個完整的初始化函數(shù):你可以在不用另外聲明的情況下將它寫在表達(dá)式中。因此,也可以進(jìn)行語法嵌套,為一個成員變量的初始值寫入初始化對象中。經(jīng)典的Point和Rectangle類的事例說明了這一點(Listing 2-30)
Listing 2-30: Nested object initializers
public?class?Point?{
????int?x,?y;
????public?int?X?{?get?{?return?x;?}?set?{?x?=?value;?}?}
????public?int?Y?{?get?{?return?y;?}?set?{?y?=?value;?}?}
}
?
public?class?Rectangle?{
????Point?tl,?br;
????public?Point?TL?{?get?{?return?tl;?}?set?{?tl?=?value;?}?}
????public?Point?BR?{?get?{?return?br;?}?set?{?br?=?value;?}?}
}
?
//?Possible?code?inside?a?method
Rectangle?r?=?new?Rectangle?{
????TL?=?new?Point?{?X?=?0,?Y?=?1?},
????BR?=?new?Point?{?X?=?2,?Y?=?3?}
};
這個對r變量的初始化和下面代碼是等價的:
?
Rectangle?rectangle2?=?new?Rectangle();Point?point1?=?new?Point();
point1.X?=?0;
point1.Y?=?1;
rectangle2.TL?=?point1;
Point?point2?=?new?Point();
point2.X?=?2;
point2.Y?=?3;
rectangle2.BR?=?point2;
Rectangle?rectangle1?=?rectangle2;
如上面代碼如示,用最短的代碼來實現(xiàn)的語法對于程序的可讀性來說是非常有幫助的。在對象初始化器中,兩個臨時變量point1和point2依然是被創(chuàng)建了,但是我們卻沒有顯示的對它們進(jìn)行定義。
前面的事例通過引用類型使用了嵌套的對象初始器。同樣的語法也適用于值類型,但是你必須明白,在TL和BR變量在初始化時,一個臨時的Point對象的拷貝被創(chuàng)建了。
小貼士在對于大的值類型進(jìn)行值復(fù)制時可能會有性能影響。但是這影響并不是因為使用對象初始化器產(chǎn)生的。
對象初始化語法僅可以用在在對字段或變量的值進(jìn)行初始化時。關(guān)鍵字new僅在最終聲明時才是必須的。在初始化器中,在對象的成員初始化時你可以不使用new關(guān)鍵字。在這種情況下,代碼就使用了通過構(gòu)造函數(shù)而創(chuàng)建的對象實例。如Listing2-31所示:
Listing 2-31: Initializers for owned objects
?
public?class?Rectangle?{
????Point?tl?=?new?Point();
????Point?br?=?new?Point();
????public?Point?TL?{?get?{?return?tl;?}?}
????public?Point?BR?{?get?{?return?br;?}?}
????}
?
//?Possible?code?inside?a?method
Rectangle?r?=?new?Rectangle?{
????TL?=?{?X?=?0,?Y?=?1?},
????BR?=?{?X?=?2,?Y?=?3?}
};
TL和BR成員實例通過Rectangle類的構(gòu)造函數(shù)被顯示地創(chuàng)建。對象初始化器不需要使用new關(guān)鍵字。這樣,初始化器就對已經(jīng)存在的實例TL和BR進(jìn)行操作。
到現(xiàn)在為止,該事例中我們在對象初始化器中使用了一些常量。你也可以使用其他的計算值,如下所示:
?
Customer?c3?=?new?Customer{????????Name?=?c1.Name,?Country?=?c2.Country,?Age?=?c2.Age?};
C#1.x已經(jīng)有了和初始化器的概念,并且語法也和這個相類型,但是它僅限于數(shù)組:
?
int[]?integers?=?{?1,?3,?9,?18?};string[]?customers?=?{?"Jack",?"Paolo",?"Marco"?};
同樣的新對象初始化器語法也可以對集合(collections)使用。內(nèi)部列表可以由常數(shù),表達(dá)式或者其他的初始化值組成,就像我們剛才展示的其他的對象初始化器一樣。如果集合類實現(xiàn)了System.Collections.Generic.ICollection<T>接口,對于在初始化器中的每個元素來說,對于ICollection<T>.Add(T)的調(diào)用是和元素的順序相同的。Add()方法在初始化器中為每個元素調(diào)用。在Listing2-32中展示了使用集合的事例。
Listing 2-32: Collection initializers
?
//?Collection?classes?that?implement?ICollection<T>
List<int>?integers?=?new?List<int>?{?1,?3,?9,?18?};
?
List<Customer>?list?=?new?List<Customer>?{
????new?Customer(?"Jack",?28?)?{?Country?=?"USA"},
????new?Customer?{?Name?=?"Paolo"?},
????new?Customer?{?Name?=?"Marco",?Country?=?"Italy"?},
};
?
//?Collection?classes?that?implement?IEnumerable
ArrayList?integers?=?new?ArrayList()?{?1,?3,?9,?18?};
?
ArrayList?list?=?new?ArrayList?{
????new?Customer(?"Jack",?28?)?{?Country?=?"USA"},
????new?Customer?{?Name?=?"Paolo"?},
????new?Customer?{?Name?=?"Marco",?Country?=?"Italy"?},
};
總的來說,對象和集合初始化器允許在一個單獨的函數(shù)中對一組對象(即便是嵌套的)進(jìn)行創(chuàng)建和初始化。LINQ對這種特性進(jìn)行了擴展,特別是通過匿名方法(anonymous types)。
匿名方法(Anonymous Types)
對象初始化器也可以在不指明類的情況下使用。若那樣做的話,一個新類-匿名類型-就被創(chuàng)建了。請思考Listing 2-33所示的代碼:
Listing 2-33: Anonymous types definition
?
Customer?c1?=?new?Customer?{?Name?=?"Marco"?};
var?c2?=?new?Customer?{?Name?=?"Paolo"?};
var?c3?=?new?{?Name?=?"Tom",?Age?=?31?};
var?c4?=?new?{?c2.Name,?c2.Age?};
var?c5?=?new?{?c1.Name,?c1.Country?};
var?c6?=?new?{?c1.Country,?c1.Name?};
c1 和 c2兩個變量是Customer類型的,但是c3, c4, c5,和 c6就不能通過代碼輕易地讀出來它們的類型了。關(guān)鍵字var應(yīng)該從一個指定的表達(dá)式中去推斷變量的類型,但是這里它有一個沒有指明類型的new關(guān)鍵字。像你想象的那樣,這種類型的對象初始化器將生成一個新類。
生成的新類有公共有屬性和在初始化器中存在的各個參數(shù)的隱藏的私有字段:它的名字和類型是從對象初始化器本身推斷出來的。當(dāng)名字不太明確時,它將從初始化表達(dá)式卻推斷,如c4,c5和c6的定義。這種較短的語法是叫做初始化器的投影,因為它不僅投影了一個值,還投影了該值的名字。
對于所有可能的屬性有相同名稱和類型的匿名類型,那個類同樣適用。用下面的代碼我們可以看到自動生成的類型的名稱:
?
Console.WriteLine(?"c1?is?{0}",?c1.GetType()?);Console.WriteLine(?"c2?is?{0}",?c2.GetType()?);
Console.WriteLine(?"c3?is?{0}",?c3.GetType()?);
Console.WriteLine(?"c4?is?{0}",?c4.GetType()?);
Console.WriteLine(?"c5?is?{0}",?c5.GetType()?);
Console.WriteLine(?"c6?is?{0}",?c6.GetType()?);
下面是輸出的內(nèi)容:
c1 is Customer c2 is Customer c3 is <>f__AnonymousType0`2[System.String,System.Int32] c4 is <>f__AnonymousType0`2[System.String,System.Int32] c5 is <>f__AnonymousType5`2[System.String,System.String] c6 is <>f__AnonymousTypea`2[System.String,System.String]匿名類型不可以通過代碼推測(因為你并不知道它生成的名稱),但是它可以在對象實例上進(jìn)行查詢。變量c3和c4是相同的匿名類型,因為它們有相同的字段和屬性。即便c5和c6有相同的屬性(類型和名稱),但是因為它們的順序不同,僅此一點,編譯器就生成兩個不同類型的匿名類型。
重要通常來說,類型中的成員的順序并不重要,即使標(biāo)準(zhǔn)對象的初始化器是基于成員的名稱而非它們的順序。LINQ為兩個僅在成員變量的順序上不同的類獲取不同類型的需要源自于一個有序的字段組,比如在SElECT語句中。
初始化一個有類型數(shù)組的語法在C#3.0中已經(jīng)得到了加強。現(xiàn)在你可以聲明一個數(shù)組初始化器,并且從初始化器內(nèi)容中引用該類型。這種結(jié)構(gòu)可以和匿名類型和對象初始化器關(guān)聯(lián)起來,如Listing2-34所示:
Listing 2-34: Implicitly typed arrays
?
var?ints?=?new[]?{?1,?2,?3,?4?};
var?ca1?=?new[]?{
????new?Customer?{?Name?=?"Marco",?Country?=?"Italy"?},
????new?Customer?{?Name?=?"Tom",?Country?=?"USA"?},
????new?Customer?{?Name?=?"Paolo",?Country?=?"Italy"?}
};
var?ca2?=?new[]?{
????new?{?Name?=?"Marco",?Sports?=?new[]?{?"Tennis",?"Spinning"}?},
????new?{?Name?=?"Tom",?Sports?=?new[]?{?"Rugby",?"Squash",?"Baseball"?}?},
????new?{?Name?=?"Paolo",?Sports?=?new[]?{?"Skateboard",?"Windsurf"?}?}
};
小貼士: C#1.x中的語法需要指定的變量為一個確定的類型。C#3.0的語法允許使用var關(guān)鍵字來以這種方式初始化的變量。
ints是一個int的數(shù)組,ca1是Customers的數(shù)組,ca2是一個匿名類型的數(shù)組,每一個都包括一個字符串類型(Name)和一個字符串?dāng)?shù)組(Sports)。在ca2的定義中你看不到類型的定義,因為所有的類型都是從初始化表達(dá)式中推斷出來的。重新看一下ca2,注意ca2的聲明是一個單獨的表達(dá)式,它可以被嵌入到另外一個中。
查詢表達(dá)式(Query Expressions)
C#3.0引入了查詢表達(dá)式(query expressions)的概念,它和SQL語法相類似,用來對數(shù)據(jù)進(jìn)行操作。這個語法被轉(zhuǎn)換成C#3.0中的常規(guī)語法,用來對作為LINQ字典的一部分的類,方法和數(shù)組進(jìn)行操作。我們不能對所有的關(guān)鍵字都一一進(jìn)行作詳細(xì)的介紹,它超出了本章的范圍。在第四章中“LINQ Syntax Fundamentals”中將對查詢表達(dá)式的語法作更進(jìn)一步的介紹。
在本小節(jié)中,我們想簡要地介紹一下編譯器對查詢表達(dá)式進(jìn)行的轉(zhuǎn)換,描述一下代碼是如何被解釋的。
下面是一個典型的LINQ查詢:
?
//?Declaration?and?initialization?of?an?array?of?anonymous?typesvar?customers?=?new?[]{
????new?{??Name?=?"Marco",?Discount?=?4.5?},
????new?{??Name?=?"Paolo",?Discount?=?3.0?},
????new?{??Name?=?"Tom",?Discount?=?3.5?}
};
?
?var?query?=
????from?c?in?customers
????where?c.Discount?>?3
????orderby?c.Discount
????select?new?{?c.Name,?Perc?=?c.Discount?/?100?};
?
foreach(?var?x?in?query?)?{
????Console.WriteLine(?x?);
}
查詢表達(dá)式以from關(guān)鍵字開始(在C#中,所有的查詢表達(dá)式都是區(qū)分大小寫的),以select或者group關(guān)鍵字結(jié)束。from關(guān)鍵字表明了LINQ將操作于哪個對象,該對象必須是一個實現(xiàn)了IEnumerable<T>接口的類的實例。
該代碼的運行結(jié)果如下:
{ Name = Tom, Perc = 0.035 } { Name = Marco, Perc = 0.045 }C#3.0將表達(dá)式解釋為如下方式:
?
var?query?=?customers????????????.Where(?c?=>?c.Discount?>?3)
????????????.OrderBy(?c?=>?c.Discount?)
????????????.Select(?c=>?new?{?c.Name,?Perc?=?c.Discount?/?100?}?);
每個查詢語法都和一個泛型方法相關(guān)聯(lián),通過適用于擴展方法的規(guī)則來解決關(guān)聯(lián)問題。因此,即使它因為可以推斷多種定義比如在lambda表達(dá)式中的參數(shù)名而顯得更加智能,查詢語法同宏擴展相類似。
在這一點上,必須清楚為什么C#3.0允許你將一個復(fù)雜的查詢寫入一個簡單的表達(dá)式的特性對于LINQ來說如此重要。一個查詢表達(dá)式調(diào)用了如此多的方法,每個調(diào)用都將前一個調(diào)用的結(jié)果作為一個參數(shù)。擴展方法將語法更加簡單化,避免嵌套調(diào)用。Lambda表達(dá)式定義了一些操作的邏輯(比如where,orderby等)。匿名方法和對象初始化器定義了如何存儲查詢的結(jié)果。本地類型推斷是將這些部分結(jié)合在一起的粘合劑。
本章小結(jié)
在本章中,我們重溫了C#1.x和2.0中的一些概念,比如泛型,匿名方法和迭代器以及yield關(guān)鍵字。這些概念對于理解C#3.0的擴展是非常重要的。我們還涉及了C#3.0的一些新特性,這些特性是LINQ的基礎(chǔ):本地類型推斷,lambda表達(dá)式,擴展方法,對象初始化器和匿名類型。
在C#3.0中更多的變化是查詢表達(dá)式。我們將在第四章中對它及LINQ架構(gòu)進(jìn)一步進(jìn)行闡述。
?
馬上熄燈了。
轉(zhuǎn)載于:https://www.cnblogs.com/hanxianlong/archive/2008/03/15/1107980.html
總結(jié)
以上是生活随笔為你收集整理的微软免费图书《Introducing Microsoft LINQ》翻译Chapter2.1:C# 3.0 特性(对象初始化表达式\匿名类型\查询表达式)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谈新公司的人才队伍建设
- 下一篇: The request failed w