次の方法で共有


LINQ の概要

Language-Integrated クエリ (LINQ) には、言語レベルのクエリ機能と、API C# と Visual Basic の上位 関数が用意されており、これを使用して表現型の宣言型コードを記述できます。

言語レベルのクエリ構文

これは言語レベルのクエリ構文です。

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 つのボーナスは、コードの簡潔さです。 上記のように、コードベースの大部分を 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 ドキュメントを手動で走査してこのタスクを実行するコードを記述することは、はるかに困難です。

LINQ プロバイダーでできることは、XML の操作だけではありません。 Linq to SQL は、MSSQL Server データベースの必要最低限のオブジェクト リレーショナル マッパー (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 構文が使用されている
  • クエリ内で変数のスコープを設定する必要はありません
  • API 構文を好むことで、コードベースから注意が逸れることはありません。

Essential LINQ

次の例は、LINQ の重要な部分の一部を簡単に示しています。 LINQ ではここで紹介する機能よりも多くの機能が提供されるため、これはまったく包括的ではありません。

パンとバター - WhereSelect、および 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())

2 つの集合間の交差

// 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)

インスタンス プロパティの等価性

最後に、より高度なサンプル: 同じ型の 2 つのインスタンスのプロパティの値が等しいかどうかを判断します (この 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 式の並列実行エンジンです。 つまり、正規表現は、任意の数のスレッド間で簡単に並列化できます。 式の前に 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 をパーティション分割し、各スレッドのいいね!の合計を並列で合計し、各スレッドによって計算された結果を合計し、その結果を適切な文字列にプロジェクトします。

図で表すと次のようになります。

PLINQ の図

LINQ を使用して簡単に表現できる並列化可能な CPU バインド ジョブ (つまり、純粋な関数であり、副作用がない) は、PLINQ の優れた候補です。 副作用のあるジョブの場合は、タスク並列ライブラリの使用を検討してください。

その他のリソース

  • Linqpad、プレイグラウンド環境、C#/F#/Visual Basic 用データベース クエリ エンジン
  • EduLinqは、LINQ to オブジェクトの実装方法を学習するための電子書籍です。