【LINQ技術】擴充套件特性和LINQ操作符

nomasp發表於2015-05-03

LINQ特有的程式設計結構

LINQ就像是嵌入到C#中的強型別查詢語言,儘管和SQL查詢很像,但語法卻並不相同,甚至還有截然相反的一面。

LINQ是在.NET發展到3.5版的時候被引進的,C#和VB語言都為此做了許多工作,擴充套件了大量新的程式設計結構。

一、隱式型別本地變數

var——一個如此小巧的關鍵字卻有著強大的力量。

var varInt=1;
var varBool=True;
var varString="String, String, String";

Console.WriteLine("varInt is a: {0}",varInt.GetType().Name);
Console.WriteLine("varBool is a: {0}",varBool.GetType().Name);
Console.WriteLine("varString is a: {0}",varString.GetType().Name);

上面的程式碼會很神奇的自動顯示出它們各自的型別。

var的限制:

1.var不能用於欄位資料
2.var不能用於返回值或引數型別
3.必須在宣告時分配值,且值不為NULL

但var可以這樣:

var alarmClock=new AlarmClock();
alarmClock=null;
var varInt=1;
var varInt2=varInt;

bool varBool=True;
var varBool2=varBool;
static int Alarm()
{
    var alarm="09:20";
    return alarm;
}

隱式型別資料是強型別資料。型別推斷延續了C#語言的強型別特性,並且只會在編譯時影響變數的宣告。之後,該資料點被視為它宣告的型別。為該變數分配不同的型別將導致編譯時錯誤。

// 編譯器知道“s”是一個string型別
var s="This is a string.";
s="Funny...";

// 因此可以呼叫string的所有成員
string bigS=s.ToUpper();

// 但不能將非string型別的資料分配給s
s=True; 

var為LINQ而生

LINQ技術使用的是查詢表示式,它可以根據表示式本身的格式產生動態建立的結果集。但有時在某些情況下根本無法顯示定義查詢的訪問型別,這時隱式型別就會發揮作用了。

二、物件和集合初始化語法

在擴充套件這個新的特性之前,我們要建立一個物件並給其屬性初始化會是這樣:

var rect = new Rect();
rect.Height=100;
rect.Width=200;

但是支援C# 3.0加入了這個新特性,程式碼就成了這樣:

var rect = new Rect(){Height=100,Width=200};

如果有建構函式的話還可以在括號內傳入引數呢,就像這樣:

var rect = new Rect("bigRect"){Height=100,Width=200};

如果對於集合,那就優勢就更加明顯了。

List<Alarm> alarmList = new List<Alarm>
{
    new Alarm { Name = "todayAlarm", Description=new Desc{ Time="07:20",Location="Home"}},
    new Alarm { Name = "tomorrowAlarm", Description=new Desc{
Time="08:40",Location="Company"}},
    new Alarm { Name = "nextYearAlarm", Description=new Desc{
Time="18:40",Location="Shanghai"}},
};

將物件/集合初始化語法和隱式型別本地變數相結合就可以宣告匿名型別。

三、匿名型別

通過這個特性可以快速建立資料的“結構”,編譯器將根據名稱/值對的集合在編譯時生成的類。該型別是基於值的語義構建的,因此System.Object中的每個虛方法都要重寫。要定義一個匿名型別,可以宣告一個隱式型別變數,並使用物件初始化語法指定資料的結構。

var alarm=new 
{
    currentTime=DateTime.Now,
    alarm=new {Name="todayAlarm",Location="Shanghai",Time="18:20"},
};

四、擴充套件方法

通過物件導向的繼承機制,我們可以給一個類新增新的方法等,但這不是唯一的方法。

C#的擴充套件方法不用子類就能向已知型別中新增新的功能,當然了,它還可以向不能有子類的密封類和結構中新增新的功能。但是需要注意的是:

1.在寫擴充套件方法時,第一個引數必須使用this限定符,用來表示被擴充套件的型別
2.第一個引數不能有ref或者out的修飾符
3.第一引數還不能是指標型別
2.擴充套件方法只能存在於靜態類中,並且必須使用static關鍵字將方法宣告為靜態的

而且使用擴充套件方法並不會影響效能,因為這些都是編譯器需要做的,而通過繼承則需要影響效能。

五、Lambda表示式

Lambda表示式可算是Lisp語言的核心了,如果想要了解該語言可以訪問我的其他部落格。C#新增了這個特性可謂是有了質的提升。

Lambda大大簡化了.NET委託的使用,減少了需要手工輸入的程式碼。

List<int> list=new List<int>();
list.AddRange(new int[]{10,21,4,8,3,59});

List<int> list2=list.FindAll(i=>(i%2)==1);

Console.WriteLine("Here are your odd numbers:");
foreach(int n in list2)
{
    Console.Write("{0}\t",n);
}

C# LINQ查詢操作符是呼叫System.Linq.Enumerable類中方法的簡便方式,這些方法通常都使用委託作為引數,用來處理資料生成正確的結果集。

LINQ的用途

1.資料

作為軟體開發者,程式設計的絕大部分時間中都在操作著資料。資料有哪些來源?我們可能會從使用者輸入中得到資料,也可能從配置檔案中得到資料,還可能從網路中得到資料,甚至可能從WCF服務返回的記憶體中得到資料。但是在操作特定的資料時,我們往往會使用不同的API。

資料 運算元據的方式
關係資料 System.Data.dll和System.Data.SqlClient.dll等
XML文件資料 System.Xml.dll
後設資料表 System.Reflection名稱空間
物件集合 System.Array和System.Collections/System.Collections.Generic

我們可以使用ADO.NET、XML名稱空間、Reflection(反射)服務還有各種對於集合的操作。但就這些API本身而言,它們都是獨立的個體。而LINQ API則傾向於提供一個同一且對稱的方式,以便我們能夠在廣義的資料上得到和操作“資料”。通過使用LINQ,我們便可以直接建立被稱為查詢表示式(query expression)的實體。這些查詢表示式是基於許多查詢操作符(query operator)的,而且有意設計為類似SQL表示式。

而根據LINQ查詢的應用場景,可以分為以下5個部分:

LINQ to Object: 針對陣列和集合使用的LINQ查詢
LINQ to XML: 使用LINQ來操縱和查詢XML文件
LINQ to DataSet: 針對ADO.NET DataSet物件使用的LINQ查詢
LINQ to Entity: 對ADO.NET Entity Framework (EF)API使用的LINQ查詢
Parallel LINQ (PLINQ): 並行處理LINQ查詢返回的結果

2.LINQ表示式是強型別的

和傳統的SQL語句不同,LINQ查詢表示式是強型別的,所以我們必須保證這些表示式在語法上都是合理的。因此我們要充分利用Visual Studio這個IDE的智慧感知、自動感知等有用的功能。

LINQ運算元組

通過下面這個示例,我們可以將陣列中的int數值取出奇數並排序。

static void Main(string[] args)
{
    LINQDemo();
}

static void LINQDemo()
{
    int[] array = { 8, 30, 13, 35, 89, 31, 83, 58, 32, 76 };
    IEnumerable<int> subset = from a in array
                    where a % 2 == 1
                    orderby a
                    select a;
    foreach(int i in subset)
    {
         Console.Write("Item: {0}\n", i);
    }
}

最後獲得的結果的集合是由一個實現了IEnumerable< T >泛型版本的物件來表示的。前面我們介紹了var,這裡可以用嗎?當然可以。

var subset = from a in array
    where a % 2 == 1
    orderby a
    select a;

如果使用var的話,那麼在foreach中也需要將int改為var。一般來說,最好在獲取LINQ查詢結果時都使用隱式型別,但在絕大多是情況下,真正的返回值是實現了IEnumerable< T >介面的型別。

延遲執行和立即執行

在迭代內容之前,LINQ查詢表示式並不會真正進行計算。這就叫“延遲執行”,它能夠讓相同的容器執行多次相同的LINQ查詢,而始終獲得最新的結果。

static void LINQDemo()
{
    int[] array = { 8, 30, 13, 35, 89, 31, 83, 58, 32, 76 };
    var subset = from a in array
                    where a % 2 == 1
                    orderby a
                    select a;
    foreach(var i in subset)
    {
         Console.Write("Item: {0}\n", i);
    }
    Console.WriteLine();

    array[0]=71;

    foreach(var i in subset)
    {
         Console.Write("Item: {0}\n", i);
    }
    Console.WriteLine();
}

這樣一來在第二次的輸出中就會在陣列的頭部新增一個71。

這裡就是在foreach中運算的LINQ表示式,但如果希望在foreach之前就運算呢?可以呼叫Enumerable型別定義的許多擴充套件方法。它定義了ToArray< T >()、ToList< T >()和ToDictionary

static void LINQDemo()
{
    int[] array = { 8, 30, 13, 35, 89, 31, 83, 58, 32, 76 };
    int[] subsetInt = (from a in array
                    where a % 2 == 1
                    orderby a
                    select a).ToArray<int>();
    List<int> subsetList = (from a in array
                    where a % 2 == 1
                    orderby a
                    select a).ToList<int>();
}

記得將整個LINQ表示式用圓括號括起來,這樣就能將它強制轉換為正確的實際型別來呼叫Enumerable的擴充套件方法。

C#編譯器不得不說真的很強大,它能夠準確的檢測泛型項的型別引數,我們不需要指定型別引數。因此也可以像下面這樣:

int[] subsetInt = (from a in array
                    where a % 2 == 1
                    orderby a
                    select a).ToArray();

立即執行的好處會體現在當要對外部呼叫者返回LINQ查詢時。

LINQ 查詢操作符

查詢操作符 含義
from、in 用於定義任何LINQ表示式的主幹,允許從合適的容器中提取資料子集
where 用於定義從一個容器裡取出哪些項的限制條件
select 用於從容器中選擇一個序列
join、on、equals、into 基於指定的鍵來做關聯操作,但這些“關聯”不必與關聯式資料庫的資料有什麼關係
orderby、ascending、descending 允許結果子集按升序或降序排序
group、by 用特定的值來對資料分組後得到一個子集

1.獲取資料子集

使用where操作符可以從資料容器裡得到特定的子集,where後應該是運算結果為布林值的表示式。當然了,對於&& 和 || 這些操作也都是可以得。

2.投影新的資料型別

如果想要傳入的Alarm[]型別的alarm集合中得到一個只有時間和名字的結果集,可以定義一個select語句,動態生成一個新的匿名型別。

static void GetNameAndTime(Alarm[] alarm)
{
    var alarmSubset=from a in alarm select new {a.Name,a.Time};
    foreach (var i in alarmSubset)
    {
        Console.WriteLine(i.ToString());
    }
}

因為在使用投影的LINQ查詢時,我們無法知道實際的資料型別,因為它是在編譯時決定的,所以就必須使用var關鍵字。因此也就無法在建立方法時返回隱式型別。

但如果需要返回這些資料呢,難道就沒有辦法嗎?可以用前面介紹的ToArray()擴充套件方法將查詢結果轉換為.NET System.Array物件。

static Array GetAlarmProp(Alarm[] alarm)
{  
    var alarmSubset=from a in alarm select new {a.Name,a.Time};
    return alarmSubset.ToArray();
}

最後我們可以在Main()中如下呼叫和處理資料:

Array al=GetAlarmProp(alarm);
foreach( object o in al)
{
    Console.WriteLine(0);
}

3.獲取資料集中的個數

在投影一批新的資料時,比如在1000個Computer中獲取所有的Windows作業系統的,那麼如果知道它們的總數呢?

int pc=(from c in computer where c.operation="Windows" select c).Count();

4.反轉結果集

想要將最終的結果進行反轉,可以通過Enumerable類中的擴充套件方法Reverse< T >()對結果集中的項進行反轉。

static void GetNameAndTime(Alarm[] alarm)
{
    var alarmSubset=from a in alarm select new {a.Name,a.Time};
    foreach (var i in alarmSubset.Reverse())
    {
        Console.WriteLine(i.ToString());
    }
}

5.對錶達式進行排序

在前面我們曾使用orderby對獲取到的奇數進行排序,那麼為什麼直接用“orderby a”就可以排序呢?很簡單,因為預設是正序……對於字串是按字母順序,對數字就是從小到大。如果要使用逆序可以使用“orderby a descending”,正序是預設的,不過也可以加上ascending。

6.維恩圖工具

Enumerable類提供了一些擴充套件方法,可以對兩個(或多個)LINQ查詢的資料進行合併(union)、比較(difference)、連線(concatenation)和交叉(intersection)。比如:

List<string> myMoney=new List<string>{"1元","2元","5元","10元"};
List<string> yourMoney=new List<string>{"10元","20元","50元","100元"};

var ourMoney=(from m in myMoney select m).Union(from y in yourMoney select y);

這樣一來,我們的前就合併了哈……

7.移除重複

移除重複就像前面的反轉一樣:

    foreach (var i in alarmSubset.Reverse())
    {
        Console.WriteLine(i.ToString());
    }
    foreach (var i in alarmSubset.Distinct())
    {
        Console.WriteLine(i.ToString());
    }

8.聚合操作

前面的Count()就是聚合中的一個例子,此外還有Average()、Max()、Min()、Sum()等等。



感謝您的訪問,希望對您有所幫助。

歡迎大家關注或收藏、評論或點贊。


為使本文得到斧正和提問,轉載請註明出處:
http://blog.csdn.net/nomasp


相關文章