from 子句
查詢表達式必須以 from 子句開頭。此外,查詢表達式可包含也以 from 子句開頭的子查詢。from 子句指定下列各項:
- 將在其上運行查詢或子查詢的數據源。
- 表示源序列中每個元素的本地範圍變量。
範圍變量和數據源已強類型化。from 子句中引用的數據源必須具有 IEnumerable、IEnumerable < T > 類型之一,或 IQueryable < T > 等派生類型。
在下面的示例中,numbers 是數據源,num 是範圍變量。請注意,這兩個變量都已強類型化,即使使用 var 關鍵字也是如此。
static void Main( string [ ] args )
{
int [ ] Zhss = [ 2 , 6 , 9 , 4 , 3 , 8 , 10 ];
var Zx = from z in Zhss
where z <= 5
select z;
foreach ( int z in Zx )
{
Console . Write ( $"{z} " );
}
}
上例輸出:2 4 3
範圍變量
數據源實現 IEnumerable < T > 時,編譯器推斷範圍變量的類型。例如,如果源具有 IEnumerable < Customer > 類型,則範圍變量會被推斷為 Customer。僅在以下情況下必須顯式指定類型:源是 IEnumerable 等非泛型 ArrayList 類型時。
在以上示例中,num 推斷為 int 類型。由於強類型化了範圍變量,所以可以在其上調用方法,或將其用於其他操作中。例如,不再編寫 select num,而編寫 select num . ToString(),使查詢表達式返回字符串序列,而不是整數序列。或者可以編寫 select num + 10,使表達式返回序列 12 14 13。
範圍變量與 foreach 語句中的迭代變量類似,一項非常重要的區別除外:範圍變量從不實際存儲來自源的數據。它只是一種語法上的便利,讓查詢能夠描述執行查詢時將發生的情況。
複合 from 子句
在某些情況下,源序列中的每個元素可能本身就是一個序列,或者包含一個序列。例如,數據源可能是 IEnumerable < 學生 >,其中序列中的每個學生對象都包含測試分數的列表。要訪問每個 “學生” 元素的內部列表,可以使用複合 from 子句。這種方法類似於使用嵌套的 foreach 語句。可以向任一子句添加 where 或 from 子句篩選結果。下面的示例演示 “學生” 對象的序列,其中每個對象都包含一個整數內部 List,表示測驗分數。要訪問內部列表,請使用複合 from 子句。如有必要,可以在這兩個 from 子句間插入子句。
static void Main( string [ ] args )
{
List < Lei學生 > XSs =
[
new Lei學生 { 姓名 = "小白兔" , 分數 = [ 96 , 98 , 105 , 126 ]},
new Lei學生 { 姓名 = "小黑兔" , 分數 = [ 93 , 126 , 108 , 136 ]},
new Lei學生 { 姓名 = "小花兔" , 分數 = [ 83 , 150 , 101 , 128 ]},
];
var CX分數 = from XS in XSs
from FS in XS . 分數
where FS >= 100
select new
{
XM = XS . 姓名 ,
FS
};
Console . WriteLine ( "查詢:" );
foreach ( var XS in CX分數 )
{
Console . WriteLine ( $"{XS . XM}的分數:{XS . FS}" ); // 輸出所有學生中分數超過 100 分的名字和成績
}
}
public class Lei學生
{
public required string 姓名 { get; init; }
public required List<int> 分數 { get; init; }
}
使用多個 from 子句執行聯接
複合 from 子句用於訪問單個數據源中的內部集合。但是,查詢也可以包含多個 from 子句,這些子句從獨立的數據源生成補充查詢。通過此方法,可以執行使用 join 子句 無法實現的某類聯接操作。
下面的示例演示兩個 from 子句如何用於形成兩個數據源的完全交叉聯接。
static void Main( string [ ] args )
{
char [ ] Dx = { 'A' , 'B' , 'C' };
char [ ] Xx = { 'x' , 'y' , 'z' };
// LianJie查詢1 的類型是 IEnumerable < ' a >,其中 'a 表示匿名類型。這個匿名類型有兩個成員,Dx 和 Xx,類型都是 char
var LianJie查詢1 =
from D in Dx
from X in Xx
select new
{
D ,
X ,
};
// LianJie查詢2 的類型是 IEnumerable < ' a >,匿名類型。這個匿名類型有兩個參數成員( upper 和 lower ),都是 char 類型
var LianJie查詢2 =
from X in Xx
where X != 'x'
from D in Dx
select new
{
X ,
D ,
};
Console . WriteLine ( "執行查詢:" );
// 將鼠標指針放在 LianJie查詢1 上以驗證其類型
foreach ( var CX1 in LianJie查詢1 )
{
Console . WriteLine ( $"{CX1 . D} 匹配到 {CX1 . X}" );
}
// 過濾非等值連接:
Console . WriteLine ( "過濾非等值連接:" );
// 將鼠標指針放在 LianJie查詢2 上以驗證其類型
foreach ( var CX2 in LianJie查詢2 )
{
Console . WriteLine ( $"{CX2 . X} 匹配到 {CX2 . D}" );
}
}
where 子句
where 子句用在查詢表達式中,用於指定將在查詢表達式中返回數據源中的哪些元素。它將一個 boolean 條件(謂詞)應用於每個源元素(由範圍變量引用),並返回滿足指定條件的元素。一個查詢表達式可以包含多個 where 子句,一個子句可以包含多個謂詞子表達式。
示例 1
在下面的示例中,where 子句篩選出除小於五的數字外的所有數字。如果刪除 where 子句,則會返回數據源中的所有數字。表達式 num <= 5 是應用於每個元素的謂詞。
static void Main( string [ ] args )
{
int [ ] Zhss = [ 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 ];
var XYDY5 =
from z in Zhss
where z <= 5
select z;
foreach ( var z in XYDY5 )
{
Console . Write ( $"{z} " );
}
}
上例輸出:
5 4 1 3 2 0
示例 2
在單個 where 子句中,可使用 && 和 || 運算符根據需要指定任意數量的謂詞。在下面的示例中,查詢將指定兩個謂詞,以便只選擇小於五的偶數。
static void Main( string [ ] args )
{
int [ ] Zhss = [ 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 ];
var XY5and偶數 =
from z in Zhss
where z < 5 && z % 2 == 0
select z;
foreach ( var z in XY5and偶數 )
{
Console . Write ( $"{z} " );
}
Console . WriteLine ( );
var XY5and偶數2 =
from z in Zhss
where z < 5
where z % 2 == 0
select z;
foreach ( var z in XY5and偶數 )
{
Console . Write ( $"{z} " );
}
}
示例 3
一個 where 子句可以包含一個或多個返回布爾值的方法。在下面的示例中,where 子句使用一種方法來確定範圍變量的當前值是偶數還是奇數。
static void Main( string [ ] args )
{
static bool Ber偶數 ( int i ) => i % 2 == 0;
int [ ] Zhss = [ 5 , 4 , 1 , 3 , 9 , 8 , 6 , 7 , 2 , 0 ];
var CX偶數 =
from z in Zhss
where Ber偶數 ( z )
select z;
foreach ( var z in CX偶數 )
{
Console.Write( $"{z} " );
}
}
備註
where 子句是一種篩選機制。除了不能是第一個或最後一個子句外,它幾乎可以放在查詢表達式中的任何位置。where 子句可以出現在 group 子句的前面或後面,具體取決於時必須在對源元素進行分組之前還是之後來篩選源元素。
如果指定的謂詞對於數據源中的元素無效,則會發生編譯時錯誤。這是 LINQ 提供的強類型檢查的一個優點。
在編譯時,where 關鍵字將轉換為對 Where 標準查詢運算符方法的調用。
select 子句
在查詢表達式中,select 子句指定在執行查詢時產生的值的類型。根據計算所有以前的子句以及根據 select 子句本身的所有表達式得出結果。查詢表達式必須以 select 子句或 group 子句結尾。
以下示例演示查詢表達式中的簡單的 select 子句。
static void Main( string [ ] args )
{
// 生成數據源
List<int> FSs = [ 96 , 88 , 93 , 83 , 90 , 80 ];
// 創建查詢
IEnumerable<int> GF =
from FS in FSs
where FS >= 90
select FS;
// 執行查詢
foreach ( int z in GF )
{
Console.Write( $"{z} " );
}
}
select 子句生成的序列的類型確定查詢變量 queryHighScores 的類型。在最簡單的情況下,select 子句僅指定範圍變量。這將導致返回的序列包含與數據源類型相同的元素。但是,select 子句還提供了強大的機制,用於將源數據轉換(或投影)為新類型。
示例
以下示例展示 select 子句可能採用的所有不同窗體。在每個查詢中,請注意 select 子句和查詢變量(XS查詢1、XS查詢2 等)類型之間的關係。
static void Main( string [ ] args )
{
Program CX = new ( );
// 生成一個未經修改的學生的過濾序列
IEnumerable<Lei學生> XS查詢1 =
from XS in CX . XSs
where XS . ID > 111
select XS;
Console . WriteLine ( "查詢 1:選擇變量範圍" );
foreach ( Lei學生 xs in XS查詢1 )
{
Console . WriteLine ( xs . ToString ( ) );
}
// 生成過濾後的元素序列,其中只包含每個學生的一個屬性
IEnumerable<string> XS查詢2 =
from XS in CX . XSs
where XS . ID > 111
select XS . 名;
Console . WriteLine ( "\r\n查詢 2:選擇變量屬性範圍" );
foreach ( string zfc in XS查詢2 )
{
Console . WriteLine ( zfc );
}
// 生成篩選後的對象序列,這些對象是通過對每個學生的方法調用創建的
IEnumerable<Lei聯繫信息> XS查詢3 =
from XS in CX . XSs
where XS . ID > 111
select XS . HQ聯繫信息 ( CX ,XS . ID );
Console . WriteLine ( "\r\n查詢 3:選擇變量方法範圍" );
foreach ( Lei聯繫信息 lx in XS查詢3 )
{
Console . WriteLine ( lx . ToString ( ) );
}
// 從每個學生的內部數組中生成一個過濾後的 int 序列
IEnumerable<int> XS查詢4 =
from XS in CX . XSs
where XS . ID > 111
select XS . 分數 [ 0];
Console . WriteLine ( "\r\n查詢 4:選擇變量 [ 索引 ] 範圍" );
foreach ( int z in XS查詢4 )
{
Console . WriteLine ( $"第一門分數:{z}" );
}
// 生成一個過濾後的 double 序列,該序列是一個表達式的結果
IEnumerable<double> XS查詢5 =
from XS in CX . XSs
where XS . ID > 111
select XS . 分數 [ 0 ] * 1.1;
Console . WriteLine ( "\r\n查詢 5:選擇表達式1" );
foreach ( double s in XS查詢5 )
{
Console . WriteLine ( $"調整後的第一門分數:{s:N2}" );
}
// 生成一個過濾後的 double 序列,該序列是一個方法調用的結果
IEnumerable<double> XS查詢6 =
from XS in CX . XSs
where XS . ID > 111
select XS . 分數 . Average ( );
Console . WriteLine ( "\r\n查詢 6:選擇表達式2" );
foreach ( double s in XS查詢6 )
{
Console . WriteLine ( $"平均分:{s:N2}" );
}
// 生成一個過濾後的匿名類型序列,只包含每個學生的兩個屬性
var XS查詢7 =
from XS in CX . XSs
where XS . ID > 111
select new
{
XS . 姓 ,
XS . 名
};
Console . WriteLine ( "\r\n查詢 7:查詢任意類型" );
foreach ( var XM in XS查詢7 )
{
Console . WriteLine ( $"{XM . 姓},{XM . 名}" );
}
// 生成篩選後的命名對象序列,其中包含每個學生的方法返回值和屬性
// 如果需要在方法邊界傳遞查詢變量,請使用命名類型
IEnumerable<Lei分數信息> XS查詢8 =
from XS in CX . XSs
where XS .ID > 111
select new Lei分數信息
{
平均分 = XS . 分數 . Average ( ),
ID = XS . ID
};
Console . WriteLine ( "\r\n查詢 8:查詢新命名類型" );
foreach ( Lei分數信息 XM in XS查詢8 )
{
Console . WriteLine ( $"ID = {XM . ID},平均分 = {XM . 平均分}" );
}
// 生成一個過濾後的序列,其中包含出現在聯繫人列表中且平均值大於 85 的學生
IEnumerable<Lei聯繫信息> XS查詢9 =
from XS in CX . XSs
where XS . 分數 . Average ( ) > 85
join LX in CX . LXXXs on XS . ID equals LX . ID
select LX;
Console . WriteLine ( "\r\n查詢 9:選擇 join 子句的結果" );
foreach ( Lei聯繫信息 XM in XS查詢9 )
{
Console . WriteLine ( $"ID = {XM . ID},Email = {XM . Email},電話號碼:{XM . 電話}" );
}
}
public class Lei聯繫信息
{
public required int ID { get; init; }
public required string Email { get; init; }
public required string 電話 { get; init; }
public override string ToString ( )
{
return $"{Email},{電話}";
}
}
List<Lei學生> XSs =
[
new Lei學生 { 姓 = "王" , 名 = "曉羣" , ID = 111 , 分數 = new List<int> ( ) { 97 , 92 , 81 , 60 } },
new Lei學生 { 姓 = "劉" , 名 = "愛玲" , ID = 112 , 分數 = new List<int> ( ) { 75 , 84 , 91 , 39 } },
new Lei學生 { 姓 = "孫" , 名 = "明明" , ID = 113 , 分數 = new List<int> ( ) { 88 , 94 , 65 , 91 } },
new Lei學生 { 姓 = "賴" , 名 = "青青" , ID = 114 , 分數 = new List<int> ( ) { 97 , 89 , 85 , 82 } }
];
List<Lei聯繫信息> LXXXs =
[
new Lei聯繫信息 { ID = 111 , Email = "xiaobaitu@segmentfault.com" , 電話 = "123-456-78900" },
new Lei聯繫信息 { ID = 112 , Email = "xiaoheitu@segmentfault.com" , 電話 = "123-456-78901" },
new Lei聯繫信息 { ID = 113 , Email = "xiaohuitu@segmentfault.com" , 電話 = "123-456-78902" },
new Lei聯繫信息 { ID = 114 , Email = "xiaohuatu@segmentfault.com" , 電話 = "123-456-78903" },
];
public class Lei分數信息
{
public double 平均分 { get; init; }
public int ID { get; init; }
}
public class Lei學生
{
public required string 姓 { get; init; }
public required string 名 { get; init; }
public required int ID { get; init; }
public required List<int> 分數;
public Lei聯繫信息? HQ聯繫信息 ( Program app , int id )
{
Lei聯繫信息? XX =
( from ci in app . LXXXs
where ci . ID == id
select ci )
. FirstOrDefault ( );
return XX;
}
public override string ToString ( )
{
return $"{姓}{名}:{ID}";
}
}
如前面示例中的 XS查詢8 所示,有時可能想要返回序列的元素僅包含一部分源元素屬性。通過讓返回序列儘可能變小,可以減少內存需求並提高執行查詢的速度。在 select 子句中創建匿名類型並使用對象初始值設定項通過源元素中的相應屬性初始化該類型可以完成此操作。
備註
在編譯時,select 子句被轉換為 Select 標準查詢運算符的方法調用。
group 子句
group 子句返回一個 IGrouping < TKey , TElement > 對象序列,這些對象包含零個或更多與該組的鍵值匹配的項。例如,可以按照每個字符串中的第一個字母對字符串序列進行分組。在這種情況下,第一個字母就是鍵,類型為 char,並且存儲在每個 IGrouping < TKey , TElement > 對象的 Key 屬性中。編譯器可推斷鍵的類型。
可以用 group 子句結束查詢表達式,如以下示例所示:
// 查詢變量是一個 IEnumerable < IGrouping < char , 學生 > >
var XS查詢10 =
from XS in CX . XSs
group XS by XS . 姓 [ 0 ];
Console . WriteLine ( "\r\n查詢 10:group 子句的結果" );
foreach ( var XM in XS查詢10 )
{
Console . WriteLine ( $"{XM . Key}" );
}
如果要對每個組執行附加查詢操作,可使用上下文關鍵字 into 指定一個臨時標識符。使用 into 時,必須繼續編寫該查詢,並最終使用一個select 語句或另一個 group 子句結束該查詢,如以下代碼摘錄所示:
// 查詢變量是一個 IEnumerable < IGrouping < char , 學生 > >
var XS查詢11 =
from XS in CX . XSs
group XS by XS . 姓 [ 0 ] into g
orderby g . Key
select g;
Console . WriteLine ( "\r\n查詢 11:group into 子句的結果" );
foreach ( var XM in XS查詢11 )
{
Console . WriteLine ( $"{XM . Key}" );
}
枚舉查詢分組的結果
由於 group 查詢產生的 IGrouping < TKey , TElement > 對象實質上是一個由列表組成的列表,因此必須使用嵌套的 foreach 循環來訪問每一組中的各個項。外部循環用於循環訪問組鍵,內部循環用於循環訪問組本身包含的每個項。組可能具有鍵,但沒有元素。下面的 foreach 循環執行上述代碼示例中的查詢:
//使用嵌套 foreach 迭代組項。這個 IGrouping 封裝了一系列學生對象和一個 char 類型的鍵
//為了方便,var 也可以在 foreach 語句中使用
foreach ( IGrouping<char , Lei學生> XSs11 in XS查詢11 )
{
Console . WriteLine ( XSs11 . Key );
foreach ( var XS in XSs11 )
{
Console . WriteLine ( $" {XS . 姓}{XS . 名}" );
}
}
鍵類型
組鍵可以是任何類型,如字符串、內置數值類型、用户定義的命名類型或匿名類型。
按字符串分組
上述代碼示例使用 char。可輕鬆改為指定字符串鍵,如完整的姓氏:
// 與前面的例子相同,只是我們使用了整個姓氏作為鍵
// 查詢變量是一個 IEnumerable < IGrouping < string , Student > >
var XS查詢12 =
from XS in CX . XSs
group XS by XS . 姓;
按布爾值分組
下面的示例演示使用布爾值作為鍵將結果劃分成兩個組。請注意,該值由 group 子句中的子表達式生成。
static void Main(string[] args)
{
List<Lei學生> XSs = FF獲取學生列表 ( );
var Ber組查詢 =
from XS in XSs
group XS by XS . 分數 . Average ( ) >= 80;
foreach ( var GF in Ber組查詢 )
{
Console . WriteLine ( GF . Key == true ? "高平均分:" : "低平均分:" );
foreach ( var XS in GF )
{
Console . WriteLine ( $" {XS . 姓},{XS . 名}:{XS . 分數 . Average ( )}" );
}
}
}
public class Lei學生
{
public required string 姓
{ get; init; }
public required string 名
{ get; init; }
public required int ID
{ get; init; }
public required List<int> 分數;
}
public static List<Lei學生> FF獲取學生列表 ( )
{
List<Lei學生> XSs =
[
new Lei學生 { ID = 10001 , 姓 = "馬" , 名 = "小曼" , 分數 = [ 77 , 96 , 93 , 100 ] },
new Lei學生 { ID = 10002 , 姓 = "風" , 名 = "蓮蓮" , 分數 = [ 92 , 100 , 67 , 74 ] },
new Lei學生 { ID = 10003 , 姓 = "歐陽" , 名 = "倩" , 分數 = [ 84 , 64 , 82 , 73 ] },
new Lei學生 { ID = 10004 , 姓 = "夏侯" , 名 = "千里" , 分數 = [ 95 , 68 , 62 , 77 ] },
new Lei學生 { ID = 10005 , 姓 = "祝融" , 名 = "珠珠" , 分數 = [ 91 , 91 , 72 , 76 ] },
];
return XSs;
}
上例將學生按照平均分是否高於 80 分分組:
高平均分:
馬,小曼:91.5
風,蓮蓮:83.25
祝融,珠珠:82.5
低平均分:
歐陽,倩:75.75
夏侯,千里:75.5
按數值範圍分組
下一示例使用表達式創建表示百分比範圍的數值組鍵。請注意,該示例使用 let 作為方法調用結果的方便存儲位置,因此無需在 group 子句中調用該方法兩次。
static void Main(string[] args)
{
List<Lei學生> XSs = FF獲取學生列表 ( );
var Ber組查詢 =
from XS in XSs
let pjf = ( int ) XS . 分數 . Average ( )
group XS by ( pjf / 10 ) into g
orderby g . Key
select g;
foreach ( var Z分數 in Ber組查詢 )
{
int LS = Z分數 . Key * 10;
Console . WriteLine ( $"學生平均分在 {LS} 和 {LS + 10} 之間:" );
foreach ( var XS in Z分數 )
{
Console . WriteLine ( $" {XS . 姓},{XS . 名}:{XS . 分數 . Average ( )}" );
}
}
}
上例中的 Main 函數改編自上節中的例子。將平均分按照每 10 分為界分段。
按複合鍵分組
希望按照多個鍵對元素進行分組時,可使用複合鍵。使用匿名類型或命名類型來存儲鍵元素,創建複合鍵。在下面的示例中,假定已經使用名為 實名 和 城市 的兩個成員聲明瞭類 Lei個人。 group 子句會為每組姓氏和城市相同的人員創建一個單獨的組。
group Ren by new {XM = Ren . 實名 , 位置 = Ren . 城市};
如果必須將查詢變量傳遞給其他方法,請使用命名類型。使用自動實現的鍵屬性創建一個特殊類,然後重寫 Equals 和 GetHashCode 方法。還可以使用結構,在此情況下,並不嚴格要求替代這些方法。後文包含的代碼示例演示瞭如何將複合鍵與命名類型結合使用。
示例 1
下面的示例演示在沒有向組應用附加查詢邏輯時,將源數據按順序放入組中的標準模式。這稱為不帶延續的分組。字符串數組中的元素按照它們的首字母進行分組。查詢的結果是 IGrouping < TKey , TElement > 類型(包含一個 char 類型的公共 Key 屬性)和一個 IEnumerable < T > 集合(在分組中包含每個項)。
group 子句的結果是由序列組成的序列。因此,若要訪問返回的每個組中的單個元素,請在循環訪問組鍵的循環內使用嵌套的 foreach 循環,如以下示例所示。
static void Main(string[] args)
{
string [ ] DanCis = [ "apple" , "next" , "blue" , "banana" , "dog" , "name" , "nice" , "new" ];
var DanCiFenLei =
from d in DanCis
group d by d [ 0 ];
foreach ( var dcs in DanCiFenLei )
{
int Z個數 = 0;
Console . WriteLine ( $"\n下列單詞起始於字母 {dcs . Key}:" );
foreach ( var DC in dcs )
{
Console . WriteLine ( DC );
Z個數++;
}
Console . WriteLine ( $"起始於字母 {dcs . Key} 的單詞數量為 {Z個數};佔總單詞數量的 {( double ) Z個數 / DanCis . Length:P2}" );
}
}
示例 2
此示例演示在創建組之後,如何使用通過 into 實現的延續對這些組執行附加邏輯。下面的示例查詢每個組,僅選擇鍵值為元音的元素。
static void Main(string[] args)
{
string [ ] DanCis = [ "apple" , "next" , "about" , "egg" , "orange" , "image" , "umbrella" , "new" , "ease" ];
var DanCiFenLei =
from d in DanCis
group d by d [ 0 ] into Zus
where ( Zus . Key == 'a' || Zus . Key == 'e' || Zus . Key == 'i' || Zus . Key == 'o' || Zus . Key == 'u' )
select Zus;
foreach ( var dcs in DanCiFenLei )
{
int Z個數 = 0;
Console . WriteLine ( $"\n下列單詞起始於元音字母 {dcs . Key}:" );
foreach ( var DC in dcs )
{
Console . WriteLine ( DC );
Z個數++;
}
Console . WriteLine ( $"起始於元音字母 {dcs . Key} 的單詞數量為 {Z個數};佔總單詞數量的 {( double ) Z個數 / DanCis . Length:P2}" );
}
}
備註
在編譯時,group 子句轉換為對 GroupBy 方法的調用。
group 子句查詢的語法不支持自定義相等比較器。若要在查詢中使用 IEqualityComparer,請顯式使用 GroupBy 方法。
into
可使用 into 上下文關鍵字創建臨時標識符,將 group、join 或 select 子句的結果存儲至新標識符。此標識符本身可以是附加查詢命令的生成器。有時稱在 group 或 select 子句中使用新標識符為“延續”。
示例
下面的示例演示使用 into 關鍵字來啓用具有推斷類型 IGrouping 的臨時標識符 fruitGroup。通過使用該標識符,可對每個組調用 Count 方法,並且僅選擇那些包含兩個或更多個單詞的組。
{
string [ ] DanCis = [ "apple" , "next" , "about" , "egg" , "orange" , "image" , "umbrella" , "new" , "ease" ];
var DanCiFenLei =
from d in DanCis
group d by d [ 0 ] into Zus
where ( Zus . Count ( ) >= 2 )
select new {
ZF首 = Zus . Key , Z個數 = Zus . Count ( )
};
foreach ( var dcs in DanCiFenLei )
{
Console . WriteLine ( $"\n下列單詞起始於元音字母 {dcs . ZF首} 擁有 {dcs . Z個數} 個元素。" );
}
}
僅當希望對每個組執行附加查詢操作時,才需在 group 子句中使用 into。
orderby 子句
在查詢表達式中,子 orderby 句使返回的序列或子序列(組)按升序或降序排序。可以指定多個鍵來執行一個或多個輔助排序作。排序由元素類型的默認比較器執行。默認的排序順序為升序。還可以指定自定義比較器。但是,它只能通過使用基於方法的語法來使用。
示例 1
在以下示例中,第一個查詢按字母順序對從 A 開始的單詞進行排序,第二個查詢按降序對相同的單詞進行排序(關鍵字 ascending 是默認排序值,可以省略)。
{
string [ ] zfc水果 = [ "cherry" , "apple" , "blueberry" ];
IEnumerable < string > CHX正常排序 =
from SG in zfc水果
orderby SG
select SG;
Console . WriteLine ( "正常排序(Ascending 或者未指定):" );
foreach ( string z in CHX正常排序 )
{
Console.Write( $"{z} " );
}
IEnumerable < string > CHX反排序 =
from SG in zfc水果
orderby SG descending
select SG;
Console . WriteLine ( "\n反排序(Descending):" );
foreach ( string z in CHX反排序 )
{
Console . Write ( $"{z} " );
}
}
示例 2
以下示例對學生的姓氏執行主要排序,然後對學生的名字執行次要排序。
static void Main(string[] args)
{
List < YG > ygs = HQ員工列表 ( );
IEnumerable < YG > CHX姓名排序 =
from YG in ygs
orderby YG . 姓 ascending , YG . 名 ascending
select YG;
Console . WriteLine ( "排序好的員工:" );
foreach ( YG y in CHX姓名排序 )
Console . WriteLine ( $"{y . 姓} {y . 名}" );
// 現在創建組並對組進行排序。該查詢首先對所有學生的名字進行排序,以便在分組後按字母順序排列。第二個 orderby 將組鍵按 alpha 順序排序
var Z排序 =
from YG in ygs
orderby YG . 姓 , YG . 名
group YG by YG . 姓 into Z姓
orderby Z姓 . Key
select Z姓;
Console . WriteLine ( $"\n排序好的組:" );
foreach ( var Z員工 in Z排序 )
{
Console . WriteLine ( Z員工 . Key );
foreach ( var Y in Z員工 )
{
Console . WriteLine ( $"{Y . 姓}{Y . 名}→{Y . ID:000}" );
}
}
public class YG
{
public required string 姓
{ get; init; }
public required string 名
{ get; init; }
public int ID
{ get; set; }
}
public static List<YG> HQ員工列表 ( )
{
List < YG > YGs = new ( )
{
new YG { 姓 = "趙" , 名 = "清水" , ID = 000 },
new YG { 姓 = "李" , 名 = "冰蓮" , ID = 001 },
new YG { 姓 = "肖" , 名 = "楠楠" , ID = 002 },
new YG { 姓 = "李" , 名 = "壯壯" , ID = 003 },
new YG { 姓 = "安" , 名 = "素素" , ID = 004 },
new YG { 姓 = "趙" , 名 = "小虎" , ID = 005 },
};
return YGs;
}
註解
在編譯時,orderby 子句將轉換為調用 OrderBy 方法。orderby 子句中的多個關鍵值將轉換為 ThenBy 方法調用。
join 子句
該 join 子句可用於將不同源序列中的元素關聯到對象模型中沒有直接關係的元素。唯一的要求是每個源中的元素共享一些可比較相等的值。例如,食品經銷商可能具有特定產品的供應商列表和買家列表。例如,可以使用子 join 句創建同一指定區域中該產品的供應商和買家的列表。
join 子句將 2 個源序列作為輸入。每個序列中的元素必須是或包含一個屬性,該屬性可以與其他序列中的相應屬性進行比較。該 join 子句使用特殊 equals 關鍵字比較指定鍵是否相等。語句 join 執行的所有連接都是等值連接。join 子句輸出結果的格式取決於所使用的具體聯接類型。以下是三種最常見的聯接類型:
- 內聯
- 分組聯接
- 左外部聯接
內聯
以下示例演示了一個簡單的內部同等聯接。此查詢生成“產品名稱/類別”對的平面序列。同一類別字符串將顯示在多個元素中。如果來自 categories 某個元素沒有匹配 products 項,該類別將不會顯示在結果中。
var CHX內聯 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ZHLID
select new { zfc產品名稱 = CHP . Name , zfc類別 = ZHL . Name }; // 產生平坦序列
分組聯接
含有 into 表達式的 join 子句稱為分組聯接。
var CHX組聯接 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ZHLID into CHPZu
select new { ZHLMing = ZHL . Name , CHPs = CHPZu };
組聯接生成層次結構結果序列,該序列將左側源序列中的元素與右側源序列中的一個或多個匹配元素進行關聯。分組連接在關係術語中沒有等效項,它本質上是一系列對象數組。
如果未找到右源序列中的元素來匹配左側源中的元素,則 join 子句將為該項生成空數組。因此,分組聯接基本上仍然是一種內部同等聯接,區別在於分組聯接將結果序列組織為多個組。
如果只選擇分組聯接的結果,則可訪問各項,但無法識別結果所匹配的項。因此,通常更為有用的做法是:選擇分組聯接的結果並將其放入一個也包含該項名的新類型中,如上例所示。
當然,還可以將分組聯接的結果用作其他子查詢的生成器:
var CHX組聯結 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ZHLID into CHPZu
from CHP2 in CHPZu
where CHP2 . DanJia > 2.50M
select CHP2;
左外部聯接
在左外部聯接中,即使沒有匹配的元素位於右序列中,也返回左側源序列中的所有元素。若要在 LINQ 中執行左外部聯接,請結合使用 DefaultIfEmpty 方法與分組聯接,指定要在某個左側元素不具有匹配元素時生成的默認右側元素。可以 null 用作任何引用類型的默認值,也可以指定用户定義的默認類型。在以下示例中,將顯示用户定義的默認類型:
var CHX左外部聯結 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ZHLID into CHPZu
from XM in CHPZu . DefaultIfEmpty ( new ChanPin { Ming = String . Empty , ZHLID = 0 } )
select new { ZHLMing = ZHL . Name , CHPMing = XM . Name };
等於運算符
join 子句執行同等聯接。換句話説,匹配只能基於兩個鍵的相等性。不支持其他類型的比較,例如 “GreaterThan” 或 “!=”。為了明確所有聯接都是等聯接,join 子句使用 “equals” 關鍵字而不是 “==” 運算符。關鍵字 equals 只能在 join 條款中使用,並且它在某些重要方面與 == 運算符不同。比較字符串時, equals 具有按值進行比較的重載,運算符 == 使用引用相等性。當比較雙方具有相同的字符串變量時,equals 和 == 都會達到相同的結果:true。這是因為,當程序聲明兩個或更多等效的字符串變量時,編譯器會將所有這些變量存儲在同一位置。這稱為 “集中”。另一個重要區別是 NULL 比較:null equals null 使用 equals 運算符的計算結果為 false,而不使用計算結果為 true 的 == 運算符。最後,範圍行為不同:對於 equals,左鍵使用外部源序列,而右鍵使用內部源。外部源僅在左側 equals 的作用域中,並且內部源序列僅在右側的作用域內。
非同等聯接
可以使用多個 from 子句將新序列獨立引入到查詢中,以執行非等值連接、交叉連接和其他自定義連接操作。
對象集合聯接與關係表
在 LINQ 查詢表達式中,對對象集合執行聯接作。不能以與兩個關係表完全相同的方式 “聯接” 對象集合。在 LINQ 中,僅當兩個源序列不受任何關係綁定時,才需要顯式 join 子句。使用 LINQ to SQL 時,外鍵表在對象模型中表示為主表的屬性。例如,在 Northwind 數據庫中,Customer 表與 Orders 表具有外鍵關係。將表映射到對象模型時,Customer 類具有一個 Orders 屬性,該屬性包含與該 Customer 關聯的 Orders 集合。實際上,已經為你執行了聯接。
組合鍵
可以使用複合鍵測試多個值的相等性。複合鍵也可以在 group 子句中使用。
示例
以下示例使用相同的匹配鍵比較內部聯接、組聯接和相同數據源上的左外部聯接的結果。向這些示例添加了一些額外的代碼,以闡明控制枱顯示中的結果。
{
static void Main(string[] args)
{
Program CX = new ( );
CX . FF內聯 ( );
CX . FF分組內聯 ( );
CX . FF分組聯接 ( );
CX . FF左外部聯接1 ( );
CX . FF左外部聯接2 ( );
}
void FF內聯 ( )
{
var CHX內聯 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ID種
select new
{
種類 = ZHL . ID ,
產品 = CHP . Ming
};
Console . WriteLine ( "內聯:" );
foreach ( var XM in CHX內聯 )
{
Console . WriteLine ( $" {XM . 產品 , -10}{XM . 種類}" );
}
Console . WriteLine ( $"內聯:{CHX內聯 . Count ( )} 個項目於內聯組。" );
Console . WriteLine ( Environment . NewLine );
}
void FF分組內聯 ( )
{
var CHX分組內聯 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ID種類 into CHPZu
select CHPZu;
int Z項目總數 = 0;
Console . WriteLine ( "分組聯接:" );
foreach ( var XM組 in CHX分組內聯 )
{
Console . WriteLine ( "組:" );
foreach ( var XM in XM組 )
{
Z項目總數 ++;
Console . WriteLine ( $" {XM . Ming , -10}{XM . ID種類}" );
}
}
Console . WriteLine ( $"未成形的分組聯接:{Z項目總數} 個項目於 {CHX分組內聯 . Count ( )} 個未命名的組。" );
Console . WriteLine ( Environment . NewLine );
}
void FF分組聯接 ( )
{
var CHX分組聯接 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ID種類 into CHPZu
from C in CHPZu
orderby C . ID種類
select new
{
種類 = C . ID種類 , 產品名 = C . Ming
};
int Z項目總數 = 0;
Console . WriteLine ( "分組聯接:" );
foreach ( var XM in CHX分組聯接 )
{
Z項目總數++;
Console . WriteLine ( $" {XM . 產品名,-10}{XM . 種類}" );
}
Console . WriteLine ( $"分組聯接:{Z項目總數} 個項目於 1 個組。" );
Console . WriteLine ( Environment . NewLine );
}
void FF左外部聯接1 ( )
{
var CHX左外部聯接 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ID種類 into CHPZu
select CHPZu . DefaultIfEmpty ( new ChanPin ( ) { ID種類 = ZHL . ID , Ming = "未知" } );
int Z項目總數 = 0;
Console . WriteLine ( "左外部聯接 1:" );
foreach ( var Z in CHX左外部聯接 )
{
Console . WriteLine ( "組:" );
foreach ( var XM in Z )
{
Z項目總數++;
Console . WriteLine ( $" {XM . Ming , -10}{XM . ID種類}" );
}
}
Console . WriteLine ( $"左外部聯接 1:{Z項目總數} 個項目於 {CHX左外部聯接 . Count ( )} 個組。" );
Console . WriteLine ( Environment . NewLine );
}
void FF左外部聯接2 ( )
{
var CHX左外部聯接 =
from ZHL in ZHLs
join CHP in CHPs on ZHL . ID equals CHP . ID種類 into CHPZu
from XM in CHPZu . DefaultIfEmpty ( )
select new
{
名 = XM == null ? "未知" : XM . Ming , 種類ID = ZHL . ID
};
Console . WriteLine ( $"左外部聯接 2→{CHX左外部聯接 . Count ( )} 個項目於 1 個組:" );
int Z項目總數 = 0;
Console . WriteLine ( "左外部聯接 2:" );
foreach ( var XM in CHX左外部聯接 )
{
Z項目總數++;
Console . WriteLine ( $" {XM . 名,-10}{XM . 種類ID}" );
}
Console . WriteLine ( $"左外部聯接 2:{Z項目總數} 個項目於 1 個組。" );
Console . WriteLine ( Environment . NewLine );
}
List < ZhongLei > ZHLs =
[
new ZhongLei { ID = 1 , Ming = "茶" },
new ZhongLei { ID = 2 , Ming = "副食" },
new ZhongLei { ID = 3 , Ming = "蔬菜" },
new ZhongLei { ID = 4 , Ming = "煙" },
new ZhongLei { ID = 5 , Ming = "酒" },
];
List < ChanPin > CHPs =
[
new ChanPin { ID種類 = 1 , Ming = "普洱茶餅" },
new ChanPin { ID種類 = 1 , Ming = "茉莉花茶" },
new ChanPin { ID種類 = 2 , Ming = "海天醬油" },
new ChanPin { ID種類 = 2 , Ming = "海天黃豆醬" },
new ChanPin { ID種類 = 2 , Ming = "海天料酒" },
new ChanPin { ID種類 = 2 , Ming = "海天蠔油" },
new ChanPin { ID種類 = 3 , Ming = "豆芽" },
new ChanPin { ID種類 = 3 , Ming = "黃瓜" },
new ChanPin { ID種類 = 3 , Ming = "茄子" },
new ChanPin { ID種類 = 3 , Ming = "小西紅柿" },
new ChanPin { ID種類 = 4 , Ming = "八喜" },
new ChanPin { ID種類 = 4 , Ming = "泰山" },
new ChanPin { ID種類 = 4 , Ming = "泰山(細支)" },
new ChanPin { ID種類 = 4 , Ming = "中華" },
new ChanPin { ID種類 = 5 , Ming = "嶗山(瓶)" },
new ChanPin { ID種類 = 5 , Ming = "嶗山(易拉罐)" },
new ChanPin { ID種類 = 5 , Ming = "青島(大瓶)" },
new ChanPin { ID種類 = 5 , Ming = "青島(小瓶)" },
new ChanPin { ID種類 = 5 , Ming = "哈爾濱(易拉罐)" },
];
}
class ChanPin
{
public required string Ming
{
get; init;
}
public required int ID種類
{
get; init;
}
}
class ZhongLei
{
public required string Ming
{
get; init;
}
public required int ID
{
get; init;
}
}
註解
後面未跟 into 的 join 子句轉換為 Join 方法調用。後面跟 into 的 join 子句轉換為 GroupJoin 方法調用。
let 子句
在查詢表達式中,存儲子表達式的結果有時很有幫助,可在後續子句中使用。可以通過 let 關鍵字執行此操作,該關鍵字創建一個新的範圍變量並通過提供的表達式結果初始化該變量。使用值進行初始化後,範圍變量不能用於存儲另一個值。但是,如果範圍變量持有可查詢類型,則可以查詢該變量。
示例
以兩種方式使用以下示例 let:
- 創建一個可以查詢其自身的可枚舉類型。
- 使查詢僅調用一次範圍變量 word 上的 ToLower。如果不使用 let,則不得不調用 where 子句中的每個謂詞的 ToLower。
{
string [ ] ZFCs =
[
"A penny saved is a penny earned.",
"The early bird catches the worm.",
"The pen is mightier than the sword."
];
// 將句子分成一個單詞數組,並選擇那些首字母是元音的單詞
var CHX小寫字母起始 =
from DC in ZFCs
let D = DC . Split ( " " )
from d in D
let D2 = d . ToLower ( )
where D2 [ 0 ] == 'a' || D2 [ 0 ] == 'e' || D2 [ 0 ] == 'i' || D2 [ 0 ] == 'o' || D2 [ 0 ] == 'u'
select d;
foreach ( var Y in CHX小寫字母起始 )
{
Console.WriteLine ( $"\"{Y}\" 起始於元音。" );
}
}
ascending 和 descending
查詢表達式中的 orderby 子句中使用 ascending 或 descending 上下文關鍵字指定排序順序為從小到大或從大到小。由於 ascending 為默認排序順序,因此無需加以指定。
示例
下面的示例説明 ascending 和 descending 在 orderby 子句中的用法。
IEnumerable<string> CHX正序 =
from SHC in ShuCais
orderby SHC ascending // 可以省略 ascending
select SHC;
IEnumerable<string> CHX倒序 =
from SHC in ShuCais
orderby SHC descending
select SHC;
on
on 上下文關鍵字用於在查詢表達式的 join 子句中指定聯接條件。
示例
下面的示例説明了 on 在 join 子句中的用法。
var CHX產品 =
from ZHL in ZhongLeis
join CHP in ChanPins on ZHL . ID equals CHP . ID種類
select new { 產品名 = CHP . Ming , 種類 = ZHL . Ming };
equals
equals 上下文關鍵字用於在查詢表達式的 join 子句中比較兩個序列的元素。
示例
下面的示例説明 equals 關鍵字在 join 子句中的用法。
var CHX產品 =
from ZHL in ZhongLeis
join CHP in ChanPins on ZHL . ID equals CHP . ID種類
select new { 產品名 = CHP . Ming , 種類 = ZHL . Ming };
by
by 上下文關鍵字用於在查詢表達式的 group 子句中指定應返回項的分組方式。
示例
下面的示例演示在 group 字句中使用 by 關鍵字指定應根據每個學生的姓氏對學生分組。
var CHX姓 = from XSH in XueShengs
group XSH by XSH . Xing;
in
在以下上下文中,使用了 in 關鍵字:
- 泛型接口和委託中的泛型類型參數。
- 作為參數修飾符,它允許按引用而不是按值向方法傳遞參數。
- foreach 語句。
- LINQ 查詢表達式中的 from 子句。
- LINQ 查詢表達式中的 join 子句。