1. 程式人生 > 程式設計 >詳解LINQ入門(中篇)

詳解LINQ入門(中篇)

前 言

在上篇中簡單的分享了LINQ的基礎概念及基礎語法,如果沒有閱讀過上篇的朋友可以點選這裡。感謝大家的支援,本篇我們將更進一步的學習LINQ的一些相關特性及應用方法。廢話不多說,請往下閱讀吧。

延遲載入

在上篇中簡單的和大家提到了LINQ具有一個很有意思的特性那就是“延遲載入”(或“延遲計算”),什麼是延遲載入呢?先看來自官方的描述:延遲執行意味著表示式的計算延遲,直到真正需要它的實現值為止。是不是覺得有點生澀難理解呢?按照我個人的理解通俗的講就是,每當我們編寫好一段LINQ表示式時,此時這個表示式所代表的序列變數僅僅只是一個代理,編譯器在執行編譯時根本就不鳥這段程式碼,檢查完語法正確性後直接跳過,直到程式碼在編譯器動態執行序列變數在其他程式碼塊被呼叫時,它所代理的linq表示式才會執行。啊~~看到這裡你是不是要暈了,到底要怎麼理解啊,無廢話上程式碼:

// 已知一個序列
var array = new int[] {1,2,3};

// 編寫一段LINQ表示式獲得一個序列變數query
// 注意,這個變數僅僅是一個代理,在執行編譯的時候,編譯器檢查完
// 該程式碼的正確性後直接跳過,不再理會
var query = from arr in array
      where arr > 1
      select arr;

// 呼叫上述序列變數query,此時上述的LINQ表達才會執行。注意此時已是在
// 編譯器Runtime 的情況下執行
foreach(var q in query)
  Console.WriteLine(q.ToString());

如果你覺得上述例子不能讓你有個深刻的理解,那麼請看來自MSDN的例子

public static class LocalExtensions
{
  public static IEnumerable<string>
   ConvertCollectionToUpperCase(this IEnumerable<string> source)
  {
    foreach (string str in source)
    {
      Console.WriteLine("ToUpper: source {0}",str);
      yield return str.ToUpper();
    }
  }
}

class Program
{
  static void Main(string[] args)
  {
    string[] stringArray = { "abc","def","ghi" };
    // 這裡方法 ConvertCollectionToUpperCase 是不會在編譯時進行呼叫核查的,直到下面的foreach呼叫變數 q 此方法才會執行
    var q = from str in stringArray.ConvertCollectionToUpperCase()
        select str;

    foreach (string str in q)
      Console.WriteLine("Main: str {0}",str);
  }
  }

注意,ConvertCollectionToUpperCase 是一個靜態擴充套件方法,後續講解,如果你對.net 2.0 的 yeild 不熟悉的網上查閱吧,這裡就不做介紹了。

// 輸出結果

// ToUpper: source abc

// Main: str ABC

// ToUpper: source def

// Main: str DEF

// ToUpper: source ghi

// Main: str GHI

小結,延遲載入有好也有壞,由於是在Runtime的情況下執行序列,所以就容易造成未知異常,斷點打錯等等,所以編碼LINQ是一定要考慮到它的這個特性。

lambda 表示式

瞭解完延遲載入後,那麼現在我們需要簡單的學習一下.net 3.5 給我們帶來的新特性lambda表示式,在上篇的評論中,有園友問lambda和linq有什麼關係,在這裡其實他們沒有任何關係,是完全不同的東西,但是我們為什麼要掌握它呢?因為在後續的學習中會使用大量的lambda表達,他可以使我們的程式碼更優雅更有可讀性,大大提高了我們的編碼效率。

那麼在學習lambda之前,先來回顧一下.net 2.0給我們帶來的委託 delegate ,這個你一定不會感到陌生吧,而且一定會常用他。對於委託這裡就不做詳細的介紹了,要複習委託的在網上查閱吧。通過委託,我們可以得到一個東西“匿名方法”。咦,是不是覺得很眼熟,呵呵,用程式碼來加深回憶吧

public delegate void SomeDelegate1;
public delegate void SomeDelegate2(arg 1,arg 2);

// 匿名方法
SomeDelegate1 del1 += delegate() {...};
SomeDelegate2 del2 += delegate(arg1,arg2) {...}

上面的程式碼中我們看到在.net 2.0時代,我們可以通過delegate建立匿名方法,提高編碼的靈活性,那麼lambda和這個有什麼關係呢,lambda對匿名方法進行了昇華。看程式碼:

public delegate void SomeDelegate1;
public delegate void SomeDelegate2(arg 1,arg 2);

// 匿名方法
SomeDelegate1 del1 += () => {...};
SomeDelegate2 del2 += (arg1,arg2) => {...}

呵呵,是不是覺得有點不可思議呢,言歸正傳什麼是lambda表示式呢,來自官方的定義:“Lambda 表示式”是一個匿名函式,它可以包含表示式和語句,並且可用於建立委託或表示式樹型別。所有 Lambda 表示式都使用 Lambda 運算子 =>,該運算子讀為“goes to”。 該 Lambda 運算子的左邊是輸入引數(如果有),右邊包含表示式或語句塊。 Lambda 表示式 x => x * x 讀作“x goes to x times x”。在定義裡提到了表示式樹,這是高階晉級的話題,這裡就不做討論了,我們先把精力放在入門與實戰應用上。

常規的lambda表示式如下:

(parameters) => { }

當指定的委託型別沒有引數是表示式可以如下

() => { } 例:() => {/*執行某些方法*/}

如果表達右側花括號裡只有一個表達例如一元表示式,二元表示式等等,又或者是一個方法時那麼花括號可以省略如下:

(x) => x; // 最簡表示式

(x,y) => x == y;

() => SomeMethod();

注意,如果右側的表示式存在花括號"{}",而且委託是具有返回型別的,那麼表示式必須帶上 return 關鍵字,如下:

(x,y) => {return x == y;};

到此我們已對lambad 表示式有了一定的掌握與瞭解。那麼我們擴充套件一下,在.net 3.5中,ms 給我們提供了兩個泛型委託 分別是 Fun<T> 和 Action <T> 他們可以幫助我們省去了返回建立常用委託的麻煩,提高編碼效率。

共同點:它們至多提供委託傳遞6個引數(任意型別);

不同點:Fun 要求必須具有返回型別,而Action則必須不返回型別,規定返回 void

示例:

Fun<int,int,bool> fun = (a,b) => a==b;
Action<string> action = (p) => Console.Write(p);

小結,lambda 對我個人而言是個又愛又恨啊,不過愛多一點,它使我們寫更少的程式碼做更多的事,但是在除錯時一旦修改表示式內容,那麼當前除錯要麼停止,要麼重新開始,ms要是在這方面做得更完美些就好啦。不過它也間接提醒我們要有好的編碼設計思維。

靜態擴充套件方法

說完lambda,那麼我們就進一步瞭解一下.net 3.5的另一個新特性“靜態擴充套件方法”,什麼是靜態擴充套件方法呢,官方定義:擴充套件方法使您能夠向現有型別“新增”方法,而無需建立新的派生型別、重新編譯或以其他方式修改原始型別。 擴充套件方法是一種特殊的靜態方法,但可以像擴充套件型別上的例項方法一樣進行呼叫。簡單的說就是我們可以向一個已知的型別在不通過繼承,複寫等操作的情況下新增一個方法,以便型別的例項可以直接使用該方法。示例如下:

static class A

{

  // 這裡的p1,p2 僅作為示例,實際中我們不一定需要

  public static int ExtendMethod1(this string input,string p1,string p2)

  {

     return int.Parse(input + p1 + p2);

  }

 

   // 泛型方法

   public static TOutput,ExtendMethod2<TOutput>(this obj);

   {

      return (TOutput)obj;

   }

}

class B
{
  void Main()
  {
   var a = "1";
   var result = a.ExtendMethod1("2","3");
   // result:123
  }
}

注意,方法的 static 是必須的,而且需在靜態類裡。第一個引數 this 是必須的,緊跟著 this 後面的需要擴充套件的型別例項引數,也是必須的。至於後面的方法呼叫傳遞引數就因個人所需了。

既然我們學習了靜態擴充套件方法,那麼它和LINQ又有什麼關係呢?在System.Linq的名稱空間中提供了大量的靜態擴充套件方法,這些靜態方法本身就對linq表示式的封裝,這樣我們就可以省去了編寫簡單的linq表示式的步驟。如下:

var array = new int[]{1,3,4,5};

 

var query1 = from arr in array

       select arr;

 

var query2 = array.Select(e => e);

上面的示例中 query1 和 query2 是等價的,通過 query2 我們是不是又可以偷懶了很多,呵呵。

再來點帶where的

var array = new int[]{1,5};

 

var query1 = from arr in array

       where arr > 2

       select arr;

 

var query2 = array.Where(e => e > 2);

再來一個複合型的

var array = new int[]{1,5};

 

var max = (from arr in array.Where(e => e > 2)

      select arr).Max();

是不是覺得很cool。由於篇幅的關係在這裡就不逐一的去接受這些靜態方法了,下面是一些常用的靜態方法列表,感興趣的去MSDN查閱詳細吧。

Aggregate,All,Any,AsEnumerable,Average,Cast,Concat,Contains,Count,DefaultIfEmpty,Distinct,ElementAt,ElementAtOrDefault,Empty,Except,First,FirstOrDefault,GroupBy,GroupJoin,Intersect,Join,Last,LastOrDefault,LongCount,Max,Min,OfType,OrderBy,OrderByDescending,Range,Repeat,Reverse,Select,SelectMany,SequenceEqual,Single,SingleOrDefault,Skip,SkipWhile,Sum,Take,TakeWhile,ThenBy,ThenByDescending,ToArray,ToDictionary,ToList,ToLookup,Union,Where

Cast<T> 和 OfType<T> 靜態擴充套件方法

在最後我們還是要注意兩個常用的靜態方法Cast<T>,OfType<T> 。它們的共同點是都能把非IEnumerable<T> 型別的集合轉換成IEnumerable<T>型別,然後再

進行LINQ操作,如下

var dt = new DataTable();
dt.Columsn.Add("A",typeof(int));

var newRow1 = dt.NewRow();
newRow1["A"] = 1;

var newRow2 = dt.NewRow();
newRow2["A"] = 2;

dt.Rows.Add(newRow1);
dt.Rows.Add(newRow2);

var query1 = dt.Rows.Cast<DataRow>().Select(e=>(int)e["A"]);
var query2 = dt.Rows.OfType<DataRow>().Select(e=>(int)e["A"]);

這樣我們就可以得到看上去兩個相同的序列,在這裡要注意:MSDN上的說明存在誤導,MSDN對於OfType<T>的解釋存在偏差,實際上經本人反覆敲程式碼驗證,得到的結論是,Cast對序列進行強制轉換,一旦轉換不成功則丟擲異常。OfType則是一旦轉換不成功,則不會丟擲異常,但是將會得到一個空序列。見下面程式碼:

var arr1 = new string[] { "1","2","test" };
var arr2 = arr1.Cast<int>();
var arr3 = arr1.OfType<int>();

//通過Cast轉換,則會丟擲異常
foreach (var i in arr2)
   Console.WriteLine(i.ToString());

//通過OfType轉換,有異常但是不會丟擲並得到一個空序列
foreach (var i in arr3)
   Console.WriteLine(i.ToString());

Console.Read();

總 結

本文到此,我們已對LINQ涉及的應用有了進一步的瞭解。學習什麼是linq的延遲載入,lambda和linq是否有曖昧關係。以及靜態擴充套件方法對linq的輔助作用。也許你會問既然可以用靜態擴充套件方法替代編寫linq,那麼二者怎麼擇取呢,據磚家叫獸提議我們應該先以linq名稱空間下的靜態擴充套件方法為主,實在是很複雜的linq表示式,我們再考慮使用linq本身的表示式編寫。後續我們將分享學習LINQ更貼近實戰應用的知識,linq to dataset,linq to,linq to sql,linq to entities.

感謝您的閱讀,如果有說得不對的地方請指正。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。