LINQ圖解教程


LINQ

什么是LINQ


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

  • 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種形式。

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查詢能力。

 

***************轉摘:https://www.cnblogs.com/moonache/p/6552843.html#wiz_toc_9

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM