C#圖解教程 第十九章 LINQ

LINQ

什麼是LINQ


在關係型數據庫系統中,數據被組織放入規範化很好的表中,而且經過簡單且強大的SQL語言來進行訪問。由於數據在表中聽從某些嚴格的規則,因此SQL能夠和它們很好的配合使用。
然而,在程序中卻與數據庫相反,保存在類對象或結構中的數據差別很大。所以,沒有通用的查詢語言來從數據結構中獲取數據。從對象獲取數據的方法一直都是做爲程序的一部分而設計的。然而使用LINQ能夠很輕鬆地查詢對象集合。
以下是LINQ的重要高級特性。css

  • LINQ(發音link)表明語言集成查詢(Language Integrated Query)
  • LINQ是.NET框架的擴展,它容許咱們以使用SQL查詢數據庫的方式來查詢數據集合
  • 使用LINQ,你能夠從數據庫、程序對象集合以及XML文檔中查詢數據

例:LINQ示例程序員

class Program
{
    static void Main()
    {
        int[] numbers={2,12,5,15};
        IEnumerable<int> lowNums=
                           from n in numbers
                           where n<10
                           select n;
        foreach(var x in lowNums)
        {
            Console.WriteLine(x);
        }
    }
}

LINQ提供程序


在以前的示例中,數據源只是int數組,它是程序在內存中的對象。然而,LINQ還能夠和各類類型的數據源一塊兒工做。然而,對於每種數據源類型,在其背後必定有根據該數據源類型實現LINQ查詢的代碼模塊。這些代碼模塊叫作LINQ提供程序(provider)。
有關LINQ提供程序的要點以下數據庫

  • 微軟爲一些常見的數據源類型提供了LINQ Provider
  • 第三方在不斷提供針對各類數據源類型的LINQ Provider

本章中,咱們主要介紹LINQ並解釋如何將其用於程序對象(LINQ to Object)和XML(LINQ to XML),其餘細節和用法不作討論。數組

匿名類型

在介紹LINQ查詢特性的細節前,咱們先學習一個容許咱們建立無名類類型的特性。匿名類型(anonymous type)常常用於LINQ查詢的結果中。
第6章介紹了對象初始化語句,它容許咱們在使用對象建立表達式時初始化新類實例的字段和屬性。提醒一下,這種形式的對象建立表達式由三部分組成:new關鍵字、類名或構造函數以及對象初始化語句。對象初始化語句在一組大括號內包含了以逗號分隔的成員初始化列表。
建立匿名類型的變量使用相同的形式,可是沒有類名和構造函數。以下的代碼行演示了匿名類型的對象建立表達式:瀏覽器

沒有類名
   ↓
new {FieldProp=InitExpr,FieldProp=InitExpr,...}
              ↑
        成員初始化語句

例:建立和使用匿名類型的示例。數據結構

class Program
{
    static void Main()
    {
     必須使用var
         ↓
        var student=new{Name="Mary Jones",Age=19,Major="History"};
        Console.WriteLine("{0},Age {1},Major: {2}",student.Name,student.Age,studeng.Major);
    }
}

須要瞭解的有關匿名類型的重要事項以下。框架

  • 匿名類型只能和局部變量配合使用,不能用於類成員
  • 因爲匿名類型沒有名字,咱們必須使用var關鍵字做爲變量類型
  • 不能設置匿名類型對象的屬性。編譯器爲匿名類型建立的屬性是隻讀的

當編譯器遇到匿名類型的對象初始化語句時,它建立一個有名字的新類類型。低於每一個成員初始化語句,它推斷其類型並建立一個只讀屬性來訪問它的值。屬性和成員初始化語句具備相同名字。匿名類型被構造後,編譯器建立了這個類型的對象。
除了對象初始化語句的賦值形式,匿名類型的對象初始化語句還有其餘兩種容許的形式:簡單標識符和成員訪問表達式。這兩種形式叫作投影初始化語句(projection initializer)。下面的變量聲明演示了3種形式。ide

var student=new{Age=19,Other.Name,Major};

例:使用3總初始化語句。注意,投影初始化語句必須定義在匿名類型聲明以前。函數

class Other
{
    static public string Name="Mary Jones";
}
class Program
{
    static void Main()
    {
        string Major="History";
        var student=new{Age=19,Other.Name,Major};
        Console.WriteLine("{0},Age {1},Major: {2}",student.Name,student.Age,studeng.Major);
    }
}

若是編譯器遇到了另外一個具備相同的參數名、相同的推斷類型和相同順序的匿名類型,它會重用這個類型並直接建立新的實例,不會建立新的匿名類型。工具

方法語法和查詢語法


咱們在寫LINQ查詢時可使用兩種形式的語法:方法語法和查詢語法。

  • 方法語法(method syntax)使用標準的方法調用。這些方法是一組標準查詢運算符的方法
  • 查詢語法(query syntax)看上去和SQL語句類似
  • 在一個查詢中能夠組合兩種形式

方法語法是命令式(imperative)的,它指明瞭查詢方法調用的順序。
查詢語法是聲明式(declarative)的,即查詢描述的是你想返回的東西,但並麼有指明如何執行這個查詢。
編譯器會將使用查詢語法表示的查詢翻譯爲方法調用的形式。這兩種形式在運行時沒有性能上的差別。
微軟推薦使用查詢語法,由於它更易讀,能更清晰地代表查詢意圖,所以也更不容易出錯。然而,有些運算符必須使用方法語法來書寫。

例:方法語法和查詢語法演示

class Program
{
    static void Main()
    {
        int[] numbers={2,5,28,31,17,16,42};
        var numsQuery=from n in numbers         //查詢語法
                      where n<20
                      select n;
        var numsMethod=numbers.Where(x=>x<20);  //方法語法
        int numsCount=(from n in numbers        //兩種形式組合
                       where n<20
                       select n).Count();
        foreach(var x in numsQuery)
        {
            Console.Write("{0}, ",x);
        }
        Console.WriteLine();
        foreach(var x in numsMethod)
        {
            Console.Write("{0}, ",x);
        }
        Console.WriteLine();
        Console.WriteLine(numsCount);
    }
}

查詢變量


LINQ查詢能夠返回兩種類型的結果–能夠是一個枚舉(可枚舉的一組數據,不是枚舉類型),它知足查詢參數的項列表;也能夠是一個叫作標量(scalar)的單一值,它是知足查詢條件的結果的某種摘要形式。

例:查詢變量示例

int[] numbers={2,5,28};
IEnumerable<int> lowNums=from n in numbers //返回枚舉數
                         where n<20
                         select n;
int numsCount=(from n in numbers           //返回一個整數
               where n<20
               select n).Count();

理解查詢變量的用法很重要。在執行前面的代碼後,lowNums查詢變量不會包含查詢的結果。相反,編譯器會建立可以執行這個查詢的代碼。
查詢變量numCount包含的是真實的整數值,它只能經過真實運行查詢後得到。
區別在於查詢執行的時間,可總結以下:

  • 若是查詢表達式返回枚舉,查詢直處處理枚舉時纔會執行
  • 若是枚舉被處理屢次,查詢就會執行屢次
  • 若是在進行遍歷後,查詢執行以前數據有改動,則查詢會使用新的數據
  • 若是查詢表達式返回標量,查詢當即執行,而且把結果保存在查詢變量中

查詢表達式的結構


查詢表達式由查詢體後的from子句組成。有關查詢表達式須要瞭解一些重要事項:

  • 子句必須按照必定順序出現
  • from子句和select…group子句這兩部分是必需的
  • LINQ查詢表達式中,select子句在表達式最後。C#這麼作的緣由之一是讓Visual Studio智能感應能在咱們輸入代碼時給咱們更多選項
  • 能夠有任意多的from…let…where子句

from子句

from子句指定了要做爲數據源使用的數據集合。它還引入了迭代變量。有關from子句的要點以下:

  • 迭代變量逐個表示數據源的每一個元素
  • from子句的語法以下
    • Type是集合中元素的類型。這是可選的,由於編譯器能夠從集合來推斷類型
    • Item是迭代變量的名字
    • Items是要查詢的集合的名字。集合必須是可枚舉的,見第18章
from Type Item in Items

下圖演示了from子句的語法。類型說明符是可選的。能夠有任意多個join子句。

儘管LINQ的from子句和foreach語句很是類似,但主要不一樣點以下:

  • foreach語句命令式地指定了從第一個到最後一個按順序地訪問集合中的項。而from子句則聲明式地規定集合中的每一個項都要被訪問,但並無假定以什麼樣的順序
  • foreach語句在遇到代碼時就執行其主體,而from子句什麼也不執行。只有在程序的控制流遇到訪問查詢變量的語句時,纔會執行查詢

join子句

LINQ中的join子句和SQL中的JOIN(聯結)子句類似。不一樣的是,咱們如今不但能夠在數據庫的表上進行聯結,還能夠在集合對象上進行該操做。若是你不熟悉聯結,那麼下面的內容會幫你理清思路。
須要先了解有關聯結的語法:

  • 使用聯結來結合兩個多多個集合中的數據
  • 聯結操做接受兩個集合而後建立一個臨時的對象集合,每一個對象包含原始集合對象中的全部字段

聯結語法以下

關鍵字        關鍵字           關鍵字      關鍵字
 ↓              ↓              ↓           ↓
join Identifier in Collection2 on Field1 equals Field1
                       ↑
              指定另外的集合和ID引用它
var query=from s in students
          join c in studentsInCourses on s.StID equals c.StID

什麼是聯結

LINQ中的join接受兩個集合而後建立一個新的集合,每一個元素包含兩個原始集合中的原始成員。

例:聯結示例

class Program
{
    public class Student
    {
        public int StID;
        public string LastName;
    }
    public class CourseStudent
    {
        public string CourseName;
        public int StID;
    }
    static Student[] students=new Student[]{
        new Student{StID=1,LastName="Carson"},
        new Student{StID=2,LastName="Klassen"},
        new Student{StID=3,LastName="Fleming"},
    };
    static CourseStudent[] studentsInCourses=new CourseStudent[]{
        new CourseStudent{CourseName="Art",StID=1},
        new CourseStudent{CourseName="Art",StID=2},
        new CourseStudent{CourseName="History",StID=1},
        new CourseStudent{CourseName="History",StID=3},
        new CourseStudent{CourseName="Physics",StID=3},
    }
    static void Main()
    {
        var query=from s in students
                  join c in studentsInCourses on s.StID equals c.STID
                  where c.CourseName=="History"
                  select.LastName;
        foreach(var q in query)
        {
            Console.WriteLine("Student taking History:{0}",q);
        }
    }
}

查詢主體中的from…let…where片斷

可選的from…let…where部分是查詢主體的第一部分,能夠由任意數量的3個子句來組合–from子句、let子句和where子句。

from子句

查詢表達式從必需的from子句開始,後面跟查詢主體。主體自己能夠從任何數量的其餘from子句開始,每一個from子句都指定了一個額外的源數據集合並引入了要在以後運算的迭代變量,全部from子句的語法和含義都同樣。

例:from子句示例

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     from b in groupB
                     where a>4&&b<=8
                     select new{a,b,sum=a+b};//匿名類型對象
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

let子句

let子句接受一個表達式的運算而且把它賦值給一個須要在其餘運算中使用的標識符。let子句的語法以下:

let Identifier=Expression

例:let子句示例

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     from b in groupB
                     let sum=a+b         //在新的變量中保存結果
                     where sum==12
                     select new{a,b,sum};
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

where子句

where子句根據以後的運算來篩選指定項。
只要是在from…let…where部分中,查詢表達式能夠有多個where。

例:where子句示例

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     from b in groupB
                     let sum=a+b         
                     where sum>=11            ←條件1
                     where a==4               ←條件2
                     select new{a,b,sum};
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

orderby子句

orderby子句根據表達式按順序返回結果項。
orderby子句語法以下圖。可選的ascending和descending關鍵字設置了排序方向。表達式一般是項的一個字段。該字段不必定非得是數值字段,也能夠是字符串這樣的可排序類型。

  • orderby子句默認是升序
  • 能夠有任意多子句,它們必須用逗號分隔

例:按照學生年齡排序

class Program
{
    static void Main()
    {
        var students=new[]
        {
            new{LName="Jones",FName="Mary",Age=19,Major="History"},
            new{LName="Smith",FName="Bob",Age=20,Major="CompSci"},
            new{LName="Fleming",FName="Carol",Age=21,Major="History"},
        };
        var query=from student in students
                  orderby student.Age
                  select student;
        foreach(var s in query)
        {
            Console.WriteLine("{0},{1}: {2} - {3}",s.LName,s.FName,s.Age,s.Major);
        }
    }
}

select…group子句

select…group子句的功能以下所示。

  • select子句指定所選對象的哪部分應該被select。它能夠指定下面的任意一項
    • 整個數據項
    • 數據項的一個字段
    • 數據項的幾個字段組成的新對象(或相似其餘值)
  • group…by子句是可選的,用來指定選擇的項如何分組

例:select整個數據項

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        var students=new[]
        {
            new{LName="Jones",FName="Mary",Age=19,Major="History"},
            new{LName="Smith",FName="Bob",Age=20,Major="CompSci"},
            new{LName="Fleming",FName="Carol",Age=21,Major="History"},
        };
        var query=from s in students
                  select s;
        foreach(var s in query)
        {
            Console.WriteLine("{0},{1}: {2} , {3}",s.LName,s.FName,s.Age,s.Major);
        }
    }
}

var query=from s in students
          select s.LName;
foreach(var s in query)
{
    Console.WriteLine(s);
}

查詢中的匿名類型

查詢結果能夠由原始集合的項、項的某些字段或匿名類型組成。
例:使用select建立一個匿名類型

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        var students=new[]
        {
            new{LName="Jones",FName="Mary",Age=19,Major="History"},
            new{LName="Smith",FName="Bob",Age=20,Major="CompSci"},
            new{LName="Fleming",FName="Carol",Age=21,Major="History"},
        };
        var query=from s in students
                  select new{s.LName,s.FName,s.Major};
        foreach(var s in query)
        {
            Console.WriteLine("{0} {1} -- {2} , {3}",s.FName,s.LName,s.Major);
        }
    }
}

group子句

group子句把select的對象根據一些標準進行分組。例如,以前示例的學士數組,程序能夠根據它們的主修課程進行分組。

  • 若是項包含在查詢的結果中,它們就能夠根據某個字段的值進行分組。做爲分組依據的屬性叫作(key)
  • group子句返回的不是原始數據源中項的枚舉,而是返回能夠枚舉已經造成的項的分組的可枚舉類型
  • 分組自己是可枚舉類型,它們能夠枚舉實際的項

例:根據學士的主修課程進行分組

using System;
using System.Linq;
class Program
{
    static void Main()
    {
        var students=new[]
        {
            new{LName="Jones",FName="Mary",Age=19,Major="History"},
            new{LName="Smith",FName="Bob",Age=20,Major="CompSci"},
            new{LName="Fleming",FName="Carol",Age=21,Major="History"},
        };
        var query=from s in students
                  group s by s.Major;
        foreach(var s in query)
        {
            Console.WriteLine("{0}",s.Key);
            foreach(var t in s)
            {
                Console.WriteLine("      {0},{1}",t.LName,t.FName);
            }
        }
    }
}

查詢延續:into子句

查詢延續子句能夠接受查詢的一部分結果並賦予一個名字,從而能夠在查詢的另外一部分中使用。

例:鏈接groupA和groupB並命名爲groupAandB

class Program
{
    static void Main()
    {
        var groupA=new[]{3,4,5,6};
        var groupA=new[]{6,7,8,9};
        var someInts=from a in groupA
                     join b in groupB on a equals b
                     into groupAandB
                     from c in groupAandB
                     select c;
        foreach(var a in someInts)
        {
            Console.WriteLine(a);
        }
    }
}

輸出:6

標準查詢運算符


標準查詢運算符由一系列API方法組成,它能讓咱們查詢任何.NET數組或集合。
標準查詢運算符的重要特性以下:

  • 被查詢的集合對象叫作序列,它必須實現IEnumerable<T>接口,T是類型
  • 標準查詢運算符使用方法語法
  • 一些運算符返回IEnumerable對象(或其餘序列),而其餘的一些運算符返回標量。返回標量的運算符當即執行,並返回一個值
  • 不少操做都以一個謂詞做爲參數。謂詞是一個方法,它以對象爲參數,根據對象是否知足某條件而返回true或false

例:Sum和Count運算符的使用

class Program
{
    static int[] numbers=new int[]{2,4,6};
    static void Main()
    {
        int total=numbers.Sum();
        int howMany=number.Count();
        Console.WriteLine("Total: {0},Count: {1}",total,howMany);
    }
}

標準查詢運算符可用來操做一個或多個序列。序列指實現了IEnumerable<>接口的類型,包括List<>、Dictionary<>、Stack<>、Array等。

標準查詢運算符的簽名

System.Linq.Enumerable類聲明瞭標準查詢運算符方法。這些方法不只是一些方法,它們是擴展了IEnumerable<T>泛型類的擴展方法。
第7章和第17章介紹類擴展方法,在本節是學習如何使用擴展方法的好機會。
簡單回顧一下。擴展方法是公共的靜態方法,儘管定義在一個類中,但目的是爲另外一個類(第一個形參)增長功能。該參數前必須有關鍵字this。

例:3個標準查詢運算符的簽名

始終是public static       名字和泛型參數    第一個參數
     ↓                         ↓             ↓
public static      int       Count<T>(this IEnumerable<T> source);
public static       T        First<T>(this IEnumerable<T> source);
public static IEnumerable<T> Where<T>(this IEnumerable<T> source,...);

例:直接調用擴展方法和將其做爲擴展進行調用的不一樣

using System.Linq;
...
static void Main()
{
    int[] intArray=new int[]{3,4,5,6,7,9};
    //方法語法
    var count1=Enumerable.Count(intArray);
    var firstNum1=Enumerable.First(intArray)
    //擴展語法
    var count2=intArray.Count();
    var firstNum2=intArrya.First();
    Console.WriteLine("Count: {0},FirstNumber: {1}",count1,firstNum1);
    Console.WriteLine("Count: {0},FirstNumber: {1}",count2,firstNum2);
}

查詢表達式和標準查詢運算符

查詢表達式和方法語法能夠組合。編譯器把每一個查詢表達式翻譯成標準查詢運算符的形式。

class Program
{
    static void Main()
    {
        var numbers=new int[]{2,6,4,8,10};
        int howMany(from n in numbers
                    where n<7
                    select n).Count();
        Console.WriteLine("Count: {0}",howMany);
    }
}

將委託做爲參數

前面咱們看到,每一個運算符的第一個參數是IEnumerable<T>對象的引用,以後的參數能夠是任何類型。不少運算符接受泛型委託做爲參數(第17章)。泛型委託用於給運算符提供用戶自定義代碼。

爲了解釋這一點,咱們首先從演示Count運算符的幾種使用方式的示例開始。
Count運算符被重載且有兩種形式,第一種以前示例中用過,它有一個參數,返回集合中元素的個數。

public static int Count<T>(this IEnumerable<T> source);

然而,假設咱們但願看看數組中奇數元素的總數。Count方法必須可以檢測整數是否爲奇數。
咱們須要使用Count方法的第二種形式。以下所示,它有一個泛型委託做爲參數。調用時,咱們提供一個接受單個T類型的輸入參數並返回布爾值的委託對象。委託代碼的返回值必須指定元素是否包含在總數中。

public static int Count<T>(this IEnumerable<T> source,Func<T,bool> predicate);
class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        var countOdd=intArray.Count(n=>n%2!=0);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

LINQ預約義的委託類型

和前面示例中的Count運算符差很少,不少LINQ運算符須要咱們提供代碼來指示運算符如何執行它的操做。咱們經過委託對象做爲參數來實現。
LINQ定義了兩套泛型委託類型與標準查詢運算符一塊兒使用,即Func委託和Action委託,各有17個成員。

  • 咱們用做實參的委託對象必須是這些類型或這些形式之一
  • TR表明返回值,而且老是在類型參數列表中的最後一個
public delegate TR Func<in T1,in T2,out TR>(T1 a1,T2 a2);
                 ↑               ↑              ↑
              返回類型         類型參數        方法參數

注意返回類型參數有out關鍵字,使之能夠協變,便可以接受聲明的類型或從這個類型派生的類型。輸入參數有in關鍵字,使之能夠逆變,即你能夠接受聲明的類型或從這個類型派生的類型。

使用委託參數的示例

class Program
{
    static bool IsOdd(int x)
    {
        return x%2!=0;
    }
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        Func<int,bool>myDel=new Func<int,bool>(IsOdd);
        var countOdd=intArray.Count(myDel);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

使用Lamba表達式參數的示例

以前示例使用獨立的方法和委託來把代碼附加到運算符上。這須要聲明方法和委託對象,而後把委託對象傳遞給運算符。若是下面的條件任意一個成立,這種方法是不錯的方案:

  • 方法還必須在程序的其餘地方調用,而不只僅是用來初始化委託對象的地方
  • 函數體中的代碼語句多於一條

若是這兩個條件都不成立,咱們可能但願使用更簡潔和更局部化的方法來給運算符提供代碼,那就是Lambda表達式。

例:用Lambda表達式修改以前的示例

class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        var countOdd=intArray.Count(n=>n%2!=0);//Lambda表達式
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

咱們也能夠用匿名方法來替代Lambda表達式。然而,這種方式比較累贅,並且Lambda表達式在語義上與匿名方法徹底等價,且更簡潔,所以沒有理由再去使用匿名方法了。

class Program
{
    static void Main()
    {
        int[] intArray=new int[] {3,4,5,6,7,9};
        Func<int,bool> myDel=delegate(int x)   //匿名方法
                             {
                                 return x%2!=0;
                             };
        var countOdd=intArray.Count(myDel);
        Console.WriteLine("Count of odd numbers: {0}",countOdd);
    }
}

LINQ to XML


可擴展標記語言(XML)是存儲和交換數據的重要方法。LINQ爲語言增長了一些特性,使得XML用起來比XPath和XSLT容易得多。

  • 可使用單一語句自頂向下建立XML樹
  • 能夠不是用包含樹的XML文檔在內存中建立並操做XML
  • 能夠不是用Text子節點來建立和操做字符串節點
  • 搜索XML樹時,不須要遍歷它。只須要查詢樹並讓它返回想要的結果

儘管本書不會完整介紹XML,但在接受LINQ to XML前,我會先簡單介紹一下XML。

標記語言

標記語言(markup language)是文檔中的一組標籤,它提供有關文檔的信息並組織其內容。即標記標籤不是文檔的數據–它們包含關於數據的數據。有關數據的數據稱爲元數據
標記語言是被定義的一組標籤,旨在傳遞有關文檔內容的特定類型的元數據。例如,HTML是衆所周知的標記語言。標籤中的元數據包含了Web頁面如何在瀏覽器中呈現已經如何使用超連接在頁面中導航的信息。
XML中僅有少許預約義標籤,其餘由程序員定義,來表示特定文檔類型須要的任何元數據。只要數據的讀者和編寫者都知道標籤的含義,標籤就能夠包含任何設計者但願的有用信息。

XML基礎

XML文檔中的數據包含了一個XML樹,它主要由嵌套元素組成。
元素是XML樹的基本要素。每一個元素都有名字且包含數據,一些元素還包含其餘被嵌套元素。元素由開始和關閉標籤進行劃分。任何元素包含的數據都必須介於開始和關閉標籤之間。

  • 開始標籤 <ElementName>
  • 結束標籤 </ElementName>
  • 無內容的單個標籤 <ElementName/>

例:

   開始標籤        內容        結束標籤
      ↓            ↓            ↓
<EmployeeName>Sally Jones</EmployeeName>
<PhoneNumber/>  ←沒有內容的元素

有關XML的重要事項:

  • XML文檔必須有一個根元素包含全部其餘元素
  • XML標籤必須合理嵌套
  • 與HTML標籤不一樣,XML標籤是區分大小寫的
  • XML特性是名字/值的配對,它包含了元素的額外元數據。特性的值部分必須包含在引號內,單引號雙引號皆可
  • XML文檔中的空格是有效的。這與把空格做爲當個空格輸出的HTML不一樣
<Employees>
    <Employee>
        <Name>Bob Smith</Name>
        <PhoneNumber>408-555-1000</PhoneNumber>
        <CellPhone/>
    </Employee>
    <Employee>
        <Name>Sally Jones</Name>
        <PhoneNumber>415-555-2000</PhoneNumber>
        <PhoneNumber>415-555-2001</PhoneNumber>
    </Employee>
</Employees>

XML類

LINQ to XML能夠以兩種方式和XML配合使用。第一種是做爲簡化的XML操做API,第二種是使用本章前面看到的LINQ查詢工具。
我會先介紹API方式。
LINQ to XML API由不少表示XML樹組件的類組成。咱們主要使用3個類,XElement、XAttribute和XDocument。
下圖演示了用於構造XML樹的類以及它們如何被嵌套。

  • 可做爲XDocument節點的直接子節點
    • 大多數狀況下,下面每一個節點類型各有一個:XDeclaration節點、XDocumentType節點以及XElement節點
    • 任何數量的XProcessingInstruction節點
  • 若是在XDocument中有最高級別的XElement節點,那麼它就是XML樹中其餘元素的根
  • 根元素能夠包含任意數量的XElement、XComment或XProcessingInstruction節點,在任何級別上嵌套

除了XAttribute類,大多數用於建立XML樹的類都從一個叫作XNode的類繼承,通常在書中也叫作「XNodes」。

建立、保存、加載和顯式XML文檔

例:建立一個包含Employees節點的XML樹

using System;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument employees1=
            new XDocument(                    //建立XML文檔
                new XElement("Employees",
                    new XElement("Name","Bob Smith"),
                    new XElement("Name","Sally Jones")
                )
            );
        employees1.Save("EmployeesFile.xml"); //保存到文件
        XDocument employees2=XDocument.Load("EmployeesFile.xml");
                                       ↑
                                   靜態方法
        Console.WriteLine(employees2);         //顯式文件
    }
}

建立XML樹

例:建立XML樹

using System;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument employeeDoc=
            new XDocument(                    //建立XML文檔
                new XElement("Employees",
                    new XElement("Employee",
                        new XElement("Name","Bob Smith"),
                        new XElement("PhoneNumber","408-555-1000")),
                    new XElement("Employee",
                        new XElement("Name","Sally Jones"),
                        new XElement("PhoneNumber","415-555-2000"),
                        new XElement("PhoneNumber","415-555-2001"))
                )
            );
        Console.WriteLine(employeeDoc);
    }
}

使用XML樹的值

當咱們遍歷XML樹來獲取或修改值時才體現了XML的強大。下表給出了用於獲取數據的主要方法。

關於上表,須要注意的一些事項以下:

  • Nodes Nodes方法返回IEnumerable<object>類型的對象,由於返回的節點多是不一樣的類型,好比XElement、XComment等。咱們可使用以類型做爲參數的方法OfType(type)來指定返回某類型的節點。例如,以下代碼只能獲取XComment節點
    • IEnumerable<XComment> comments=xd.Nodes().OfType<XComment>()
  • Elements 因爲獲取XElement是很是廣泛的需求,就出現了`Nodes.OfType(XElement)()``表達式的簡短形式–Elements方法
    • 無參數的Elements方法返回全部子XElements
    • 單個name參數的Elements方法返回具備這個名字的子XElements。例如,以下代碼返回具備名字PhoneNumber的子XElement節點
    • IEnumerable<XElement> empPhones=emp.Elements("PhoneNumber");
  • Element 這個方法只獲取當前節點的第一個子XElement。若是無參數,獲取第一個XElement節點,若是帶一個參數,獲取第一個具備此名字的子XElement
  • Descendants和Ancestors 這些方法和Elements以及Parent方法差很少,只不過它們不返回直接的子元素和父元素,而是忽略嵌套級別,包括全部之下或者之上的節點
using System;
using System.Collections.Generic;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument employeeDoc=
            new XDocument(                    //建立XML文檔
                new XElement("Employees",
                    new XElement("Employee",
                        new XElement("Name","Bob Smith"),
                        new XElement("PhoneNumber","408-555-1000")),
                    new XElement("Employee",
                        new XElement("Name","Sally Jones"),
                        new XElement("PhoneNumber","415-555-2000"),
                        new XElement("PhoneNumber","415-555-2001"))
                )
            );
        //獲取第一個名爲「Employees」的子XElement 
        XElement root=employeeDoc.Element("Employees");
        IEnumerable<XElement> employees=root.Elements();
        foreach(XElement emp in employees)
        {
            XElement empNameNode=emp.Element("Name");
            Console.WriteLine(empNameNode.Value);
            IEnumerable<XElement> empPhones=emp.Elements("PhoneNumber");
            foreach(XElement phone in empPhones)
            {
                Console.WriteLine(phone.Value);
            }
        }
    }
}

增長節點以及操做XML

咱們可使用Add方法位現有元素增長子元素。

using System;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                new XElement("first")
            )
        );
        Console.WriteLine("Original tree");
        Console.WriteLine(xd);
        Console.WriteLine();
        XElement rt=xd.Element("root");
        rt.Add(new XElement("second"));
        rt.Add(new XElement("third"),
               new XComment("Important Comment"),
               new XElement("fourth"));
        Console.WriteLine("Modified tree");
        Console.WriteLine(xd);
    }
}

下表列出了最重要的一些操做XML的方法。

使用XML特性

特性提供了有關XElement節點的額外信息,它放在XML元素的開始標籤中。
咱們以函數方法構造XML樹時,只需在XElement的構造函數中包含XAttribute構造函數來增長特性。XAttribute構造函數有兩種形式一種是接受name和value,另外一種是接受現有XAttribute的引用。

例:爲root增長兩個特性。

XDocument xd=new XDocument(
    new XElement("root",
            new XAttribute("color","red"),
            new XAttribute("size","large"),
        new XElement("first"),
        new XElement("second")
    )
);

例:獲取特性

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        Console.WriteLine(xd);
        Console.WriteLine();
        XElement rt=xd.Element("root");
        XAttribute color=rt.Attribute("color");
        XAttribute size=rt.Attribute("size");
        Console.WriteLine("color is {0}",color.Value);
        Console.WriteLine("size is {0}",size.Value);
    }
}

例:移除特性

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        XElement rt=xd.Element("root");
        rt.Attribute("color").Remove();//移除color特性
        rt.SetAttributeValue("size",null);//移除size特性
        Console.WriteLine(xd);
    }
}

例:增長或改變特性的值

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("root",
                    new XAttribute("color","red"),
                    new XAttribute("size","large"),
                new XElement("first"),
            )
        );
        XElement rt=xd.Element("root");
        rt.SetAttributeValue("size","midium");  //改變特性值
        rt.SetAttributeValue("width","narrow"); //添加特性
        Console.WriteLine(xd);
    }
}

節點的其餘類型

XComment

XML註釋由<!--和-->記號間的文本組成。記號間的文本會被XML解析器忽略。咱們可使用XComment類向一個XML文檔插入文本。以下面代碼所示: 

 new XComment("This is a comment")  

這段代碼產生以下XML文檔:
 <!--This is a comment--> 

XDeclaration

XML文檔從包含XML使用的版本號、字符編碼類型以及文檔是否依賴外部引用的一行開始。這是有關XML的信息,所以它實際上是有關數據的元數據。這叫作XML聲明,可使用XDeclaration類來插入,以下代碼給出了XDeclaration的示例:
 new XDeclaration("1.0","uff-8","yes")  
這段代碼產生以下XML文檔:
 <?xml version="1.0" encoding="utf-8 " standalone="yes"?> 

XProecssingInstruction

XML處理指令用於提供XML文檔如何被使用和翻譯的額外數據,最多見的就是把處理指令用於關聯XML文檔和一個樣式表。
咱們可使用XProecssingInstruction構造函數來包含處理指令。它接受兩個字符串參數:目標和數據串。如歌處理指令接受多個數據參數,這些參數必須包含在XProecssingInstruction構造函數的第二個字符串參數中,以下的構造函數代碼所示。

 new XProecssingInstruction("xml-stylesheet",@"href=""stories"",type=""text/css""") 

這段代碼產生以下XML文檔:
 <?xml-stylesheet href="stories.css" type="text/css"?> 

例:

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XDeclaration("1.0","uff-8","yes"),
            new XComment("This is a comment"),
            new XProecssingInstruction("xml-stylesheet",@"href=""stories"",type=""text/css"""),
            new XElement("root",
                new XElement("first"),
                new XElement("second")
            )
        );
    }
}

代碼會產生以下的輸出文件。然而若是使用WriteLine(xd),聲明語句不會被打印出來。

使用LINQ to XML的LINQ 查詢

如今,咱們能夠把LINQ XML API和LINQ查詢表達式組合爲簡單而強大的XML樹搜索。

例:建立示例用XML樹

class Program
{
    static void Main()
    {
        XDocument xd=new XDocument(
            new XElement("MyElements",
                new XElement("first",
                    new XAttribute("color","red"),
                    new XAttribute("size","small")),
                new XElement("second",
                    new XAttribute("color","red"),
                    new XAttribute("size","midium")),
                new XElement("third",
                    new XAttribute("color","blue"),
                    new XAttribute("size","large"))
            )
        );
        Console.WriteLine(xd);
        xd.Save("SimpleSample.xml");
    }
}

例:LINQ to XML

class Program
{
    static void Main()
    {
        XDocument xd=XDocument.Load("SimpleSample.xml");
        XElement rt=xd.Element("MyElements");
        var xyz=from e in rt.Elements()
                where e.Name.ToString().Length==5
                select e;
        foreach(XElement x in xyz)
        {
            Console.WriteLine(x.Name.ToString());
        }
        Console.WriteLine();
        foreach(XElement x in xyz)
        {
            Console.WriteLine("Name: {0}, color: {1}, size: {2}",
                              x.Name,
                              x.Attribute("color").Value,
                              x.Attribute("size").Value);
        }
    }
}

例:獲取XML樹的全部頂層元素,併爲每一個元素建立了匿名類型對象

using System;
using System.Linq;
using System.Xml.Linq;
class Program
{
    static void Main()
    {
        XDocument xd=XDocument.Load("SimpleSample.xml");
        XElement rt=xd.Element("MyElements");
        var xyz=from e in rt.Elements()
                select new{e.Name,color=e.Attribute("color")};
                //建立匿名類型
        foreach(var x in xyz)
        {
            Console.WriteLine(x);
        }
        Console.WriteLine();
        foreach(var x in xyz)
        {
            Console.WriteLine("{0,-6},    color:{1,-7}",x.Name,x.color.Value);
        }
    }
}

從這些示例咱們能夠看到,能夠輕易地組合XML API和LIQN查詢工具來產生強大的XML查詢能力。

相關文章
相關標籤/搜索