LINQ 概觀
Language-Integrated Query(LINQ)提供語言層級的查詢功能,並引入用於 C# 和 Visual Basic 的 高階函式 API,使您能撰寫具表達性的聲明式程式碼。
語言層級查詢語法
這是語言層級查詢語法:
var linqExperts = from p in programmers
where p.IsNewToLINQ
select new LINQExpert(p);
Dim linqExperts = From p in programmers
Where p.IsNewToLINQ
Select New LINQExpert(p)
這是使用 IEnumerable<T>
API 的相同範例:
var linqExperts = programmers.Where(p => p.IsNewToLINQ)
.Select(p => new LINQExpert(p));
Dim linqExperts = programmers.Where(Function(p) p.IsNewToLINQ).
Select(Function(p) New LINQExpert(p))
LINQ 具有表達性
假設您有一個寵物列表,但想將其轉換成一個字典,您可以通過 RFID
值直接存取寵物。
這是傳統的命令式程式碼:
var petLookup = new Dictionary<int, Pet>();
foreach (var pet in pets)
{
petLookup.Add(pet.RFID, pet);
}
Dim petLookup = New Dictionary(Of Integer, Pet)()
For Each pet in pets
petLookup.Add(pet.RFID, pet)
Next
程式代碼背後的意圖並不是要創建一個新的 Dictionary<int, Pet>
並透過迴圈增加其內容,而是將現有的清單轉換成字典! LINQ 會保留意圖,而命令式程式代碼則不會。
這是對等的 LINQ 運算式:
var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)
使用 LINQ 的程式碼很有價值,因為它能夠平衡程式設計師的意圖與程式碼間的關係,使推理過程更加順暢。 另一個優點是程式代碼簡潔。 想像一下,將程式代碼基底的大部分減少 1/3,如以上所述。 甜蜜的交易,對嗎?
LINQ 提供者簡化數據存取
對於相當大部分在實際運行中的軟體,一切都圍繞著處理來自某些來源的資料(資料庫、JSON、XML 等類型)。 這通常牽涉到針對每個數據源學習新的 API,這可能會令人惱火。 LINQ 通過將數據存取的常見元素抽象化為查詢語法,簡化了操作,無論您選擇的是哪種數據源,這樣的語法看起來都一樣。
這會尋找具有特定屬性值的所有 XML 元素:
public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,
string attributeName, string value)
{
return from el in documentRoot.Elements(elementName)
where (string)el.Element(attributeName) == value
select el;
}
Public Shared Function FindAllElementsWithAttribute(documentRoot As XElement, elementName As String,
attributeName As String, value As String) As IEnumerable(Of XElement)
Return From el In documentRoot.Elements(elementName)
Where el.Element(attributeName).ToString() = value
Select el
End Function
以手動遍歷 XML 文件來撰寫程式代碼以執行這項工作將更具挑戰性。
與 XML 互動並不是您唯一可以與 LINQ 提供者互動的事情。 Linq to SQL 是 MSSQL Server 資料庫的相當裸露 Object-Relational 的對應程式 (ORM) 。 Json.NET 函式庫透過 LINQ 提供高效的 JSON 文件遍歷。 此外,如果沒有可執行所需項目的函式庫,您也可以 撰寫自己的 LINQ 提供者!
使用查詢語法的原因
為什麼要使用查詢語法? 這是經常出現的問題。 畢竟,以下的程式碼:
var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)
比這更簡潔:
var filteredItems = from item in myItems
where item.Foo
select item;
Dim filteredItems = From item In myItems
Where item.Foo
Select item
用 API 語法來執行查詢語法不是更簡潔的嗎?
不。 查詢語法允許使用 let 子句,這可讓您在表達式的範圍內引進和系結變數,並在表達式的後續片段中使用變數。 只能使用 API 語法重現相同的程式代碼,但最有可能導致難以讀取的程式代碼。
因此引發一個問題,你是否應該只使用查詢語法?
此問題的回答是 是,如果:
- 您現有的程式代碼基底已經使用查詢語法。
- 由於複雜度,您必須在查詢內設定變數的範圍。
- 您偏好查詢語法,且不會干擾程式代碼基底。
此問題的解答是 ,除非沒有...
- 您現有的程式代碼基底已經使用 API 語法
- 您不需要在查詢內設定變數的範圍
- 您偏好應用程式介面語法,並且不會干擾您的程式碼基礎。
Essential LINQ
下列範例是一些LINQ基本部分的快速示範。 這絕不是完整的,因為 LINQ 提供的功能比這裡展示的功能還要多。
麵包和黃油 - Where
、Select
和 Aggregate
// Filtering a list.
var germanShepherds = dogs.Where(dog => dog.Breed == DogBreed.GermanShepherd);
// Using the query syntax.
var queryGermanShepherds = from dog in dogs
where dog.Breed == DogBreed.GermanShepherd
select dog;
// Mapping a list from type A to type B.
var cats = dogs.Select(dog => dog.TurnIntoACat());
// Using the query syntax.
var queryCats = from dog in dogs
select dog.TurnIntoACat();
// Summing the lengths of a set of strings.
int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (partialSum, nextString) => partialSum + nextString.Length);
' Filtering a list.
Dim germanShepherds = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepherd)
' Using the query syntax.
Dim queryGermanShepherds = From dog In dogs
Where dog.Breed = DogBreed.GermanShepherd
Select dog
' Mapping a list from type A to type B.
Dim cats = dogs.Select(Function(dog) dog.TurnIntoACat())
' Using the query syntax.
Dim queryCats = From dog In dogs
Select dog.TurnIntoACat()
' Summing the lengths of a set of strings.
Dim seed As Integer = 0
Dim sumOfStrings As Integer = strings.Aggregate(seed, Function(partialSum, nextString) partialSum + nextString.Length)
將多重清單展平
// Transforms the list of kennels into a list of all their dogs.
var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);
' Transforms the list of kennels into a list of all their dogs.
Dim allDogsFromKennels = kennels.SelectMany(Function(kennel) kennel.Dogs)
兩個集合的聯集(使用自定義比較器)
public class DogHairLengthComparer : IEqualityComparer<Dog>
{
public bool Equals(Dog a, Dog b)
{
if (a == null && b == null)
{
return true;
}
else if ((a == null && b != null) ||
(a != null && b == null))
{
return false;
}
else
{
return a.HairLengthType == b.HairLengthType;
}
}
public int GetHashCode(Dog d)
{
// Default hashcode is enough here, as these are simple objects.
return d.GetHashCode();
}
}
...
// Gets all the short-haired dogs between two different kennels.
var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());
Public Class DogHairLengthComparer
Inherits IEqualityComparer(Of Dog)
Public Function Equals(a As Dog,b As Dog) As Boolean
If a Is Nothing AndAlso b Is Nothing Then
Return True
ElseIf (a Is Nothing AndAlso b IsNot Nothing) OrElse (a IsNot Nothing AndAlso b Is Nothing) Then
Return False
Else
Return a.HairLengthType = b.HairLengthType
End If
End Function
Public Function GetHashCode(d As Dog) As Integer
' Default hashcode is enough here, as these are simple objects.
Return d.GetHashCode()
End Function
End Class
...
' Gets all the short-haired dogs between two different kennels.
Dim allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, New DogHairLengthComparer())
兩組集合之間的交集
// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
new VolunteerTimeComparer());
' Gets the volunteers who spend share time with two humane societies.
Dim volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
New VolunteerTimeComparer())
訂購
// Get driving directions, ordering by if it's toll-free before estimated driving time.
var results = DirectionsProcessor.GetDirections(start, end)
.OrderBy(direction => direction.HasNoTolls)
.ThenBy(direction => direction.EstimatedTime);
' Get driving directions, ordering by if it's toll-free before estimated driving time.
Dim results = DirectionsProcessor.GetDirections(start, end).
OrderBy(Function(direction) direction.HasNoTolls).
ThenBy(Function(direction) direction.EstimatedTime)
實例屬性的相等性
最後,更進階的範例:判斷相同類型的兩個實例的屬性值是否相等(借用並修改自此 StackOverflow 帖文 ):
public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
if (self == null || to == null)
{
return self == to;
}
// Selects the properties which have unequal values into a sequence of those properties.
var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
where !ignore.Contains(property.Name)
let selfValue = property.GetValue(self, null)
let toValue = property.GetValue(to, null)
where !Equals(selfValue, toValue)
select property;
return !unequalProperties.Any();
}
<System.Runtime.CompilerServices.Extension()>
Public Function PublicInstancePropertiesEqual(Of T As Class)(self As T, [to] As T, ParamArray ignore As String()) As Boolean
If self Is Nothing OrElse [to] Is Nothing Then
Return self Is [to]
End If
' Selects the properties which have unequal values into a sequence of those properties.
Dim unequalProperties = From [property] In GetType(T).GetProperties(BindingFlags.Public Or BindingFlags.Instance)
Where Not ignore.Contains([property].Name)
Let selfValue = [property].GetValue(self, Nothing)
Let toValue = [property].GetValue([to], Nothing)
Where Not Equals(selfValue, toValue) Select [property]
Return Not unequalProperties.Any()
End Function
PLINQ
PLINQ 或 Parallel LINQ 是 LINQ 表達式的平行執行引擎。 換句話說,一般 LINQ 運算式可以跨任意數目的線程進行簡單平行處理。 這是透過調用AsParallel()
在表達式前面來完成。
請考慮下列事項:
public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)
{
var seed = default(UInt64);
Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;
Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";
return facebookUsers.AsParallel()
.Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
}
Public Shared GetAllFacebookUserLikesMessage(facebookUsers As IEnumerable(Of FacebookUser)) As String
{
Dim seed As UInt64 = 0
Dim threadAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
Dim threadResultAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
Dim resultSelector As Func(Of Uint64, string) = Function(total) $"Facebook has {total} likes!"
Return facebookUsers.AsParallel().
Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector)
}
此程式碼會視需要在系統線程間分割 facebookUsers
、平行計算每個線程上的按讚總數,加總每個線程計算的結果,並將結果轉換成一個良好的字串。
在圖示中:
可平行處理的 CPU 系結作業,可透過 LINQ 輕鬆表示(換句話說,是純函式且沒有副作用)是 PLINQ 的絕佳候選專案。 對於 有副作用的工作,請考慮使用 工作平行連結庫。