LINQ (統合言語クエリ)LINQ (Language Integrated Query)

紹介What is it?

LINQ では、言語レベルのクエリ機能と、表現力豊かな宣言コードを記述する方法として C# および Visual Basic に高階関数 API が提供されます。LINQ provides language-level querying capabilities and a higher-order function API to C# and Visual Basic as a way to write expressive, declarative code.

言語レベルのクエリ構文:Language-level query syntax:

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 を使用して表した場合の例:Same example using the 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 は表現力が豊かLINQ is Expressive

たとえば、ペットのリストはあるが、RFID 値で直接ペットにアクセスできる辞書に変換する必要があるとします。Imagine you have a list of pets, but want to convert it into a dictionary where you can access a pet directly by its RFID value.

従来の命令型コード:Traditional imperative code:

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> を作成し、ループを使用して追加することではなく、既存のリストを辞書に変換することです。The intention behind the code is not to create a new Dictionary<int, Pet> and add to it via a loop, it is to convert an existing list into a dictionary! LINQ ではこの目的が維持されますが、命令型コードでは維持されません。LINQ preserves the intention whereas the imperative code does not.

同等の LINQ 式:Equivalent LINQ expression:

var petLookup = pets.ToDictionary(pet => pet.RFID);
Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)

プログラマーとして考えると、目的とコードを同等にするため、LINQ を使用するコードのほうが有益です。The code using LINQ is valuable because it evens the playing field between intent and code when reasoning as a programmer. また、コードが簡潔であるという利点があります。Another bonus is code brevity. 上記のように、コードベースの大部分を 3 分の 1 に減らせることを考えれば、Imagine reducing large portions of a codebase by 1/3 as done above. 賢明な選択です。Pretty sweet deal, right?

LINQ プロバイダーでデータ アクセスが簡単にLINQ Providers Simplify Data Access

実環境の非常に多くのソフトウェアでは、すべてのことが一部のソース (データベース、JSON、XML など) からのデータ処理を中心に展開されます。For a significant chunk of software out in the wild, everything revolves around dealing with data from some source (Databases, JSON, XML, etc). 多くの場合、これには面倒なデータ ソースごとの新しい API の学習が含まれます。Often this involves learning a new API for each data source, which can be annoying. LINQ は、データ アクセスの一般的な要素を、選択されたデータ ソースに関係なく同じようなクエリ構文に抽象化することで、これを簡略化します。LINQ simplifies this by abstracting common elements of data access into a query syntax which looks the same no matter which data source you pick.

たとえば、特定の属性値を持つ XML 要素をすべて検索するとします。Consider the following: finding all XML elements with a specific attribute value.

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 ドキュメントを手動でスキャンし、このタスクを実行するのはとても難しいことです。Writing code to manually traverse the XML document to perform this task would be far more challenging.

XML との対話が、LINQ プロバイダーでできる唯一のことではありません。Interacting with XML isn’t the only thing you can do with LINQ Providers. Linq to SQL は、MSSQL Server データベースの必要最低限のオブジェクト リレーショナル マッパー (ORM) です。Linq to SQL is a fairly bare-bones Object-Relational Mapper (ORM) for an MSSQL Server Database. JSON.NET ライブラリでは、LINQ を使用して効率的に JSON ドキュメントをトラバースできます。The JSON.NET library provides efficient JSON Document traversal via LINQ. さらに、必要な作業を行うライブラリがない場合は、独自の LINQ プロバイダーを記述することもできます。Furthermore, if there isn’t a library which does what you need, you can also write your own LINQ Provider!

クエリ構文を使用する理由Why Use the Query Syntax?

これはよくある質問です。This is a question which often comes up. いずれにせよ、After all, this,

var filteredItems = myItems.Where(item => item.Foo);
Dim filteredItems = myItems.Where(Function(item) item.Foo)

上の構文は下の構文よりもずっと簡潔です。is a lot more concise than this:

var filteredItems = from item in myItems
                    where item.Foo
                    select item;
Dim filteredItems = From item In myItems
                    Where item.Foo
                    Select item

API 構文は単に、クエリ構文を実行するより簡潔な方法であるだけでしょうか?Isn’t the API syntax just a more concise way to do the query syntax?

いいえ。No. クエリ構文では let 句を使用できます。したがって、式の後ろの部分でこの句を使用すれば、式のスコープ内で変数を導入してバインドすることができます。The query syntax allows for the use of the let clause, which allows you to introduce and bind a variable within the scope of the expression, using it in subsequent pieces of the expression. API 構文だけでも同じコードを再現できますが、コードが読み取りにくくなる可能性が高くなります。Reproducing the same code with only the API syntax can be done, but will most likely lead to code which is hard to read.

そこで、クエリ構文を使用する必要があるかどうかですが、So this begs the question, should you just use the query syntax?

次のような場合には、使用する必要がありますThe answer to this question is yes if...

  • 既存のコードベースで既にクエリ構文を使用している。Your existing codebase already uses the query syntax
  • 複雑になるため、クエリ内で変数をスコープする必要がある。You need to scope variables within your queries due to complexity
  • クエリ構文が好ましく、コードベースから注意がそれることはない。You prefer the query syntax and it won’t distract from your codebase

次のような場合には、使用する必要はありませんThe answer to this question is no if...

  • 既存のコードベースで既に API 構文を使用している。Your existing codebase already uses the API syntax
  • クエリ内で変数をスコープする必要はない。You have no need to scope variables within your queries
  • API 構文が好ましく、コードベースから注意がそれることはない。You prefer the API syntax and it won’t distract from your codebase

重要なサンプルEssential Samples

LINQ サンプルの一覧については、「101 LINQ Samples」 (101 個の LINQ サンプル) を参照してください。For a truly comprehensive list of LINQ samples, visit 101 LINQ Samples.

以下に、LINQ の重要な要素をいくつか簡単に示します。The following is a quick demonstration of some of the essential pieces of LINQ. これは決して包括的なものではありません。LINQ ではここで紹介するものよりはるかに多くの機能が提供されます。This is in no way comprehensive, as LINQ provides significantly more functionality than what is showcased here.

  • 最も基本的かつ重要な要素 - WhereSelect、および Aggregate:The bread and butter - Where, Select, and Aggregate:
// Filtering a list.
var germanShepards = dogs.Where(dog => dog.Breed == DogBreed.GermanShepard);

// Using the query syntax.
var queryGermanShepards = from dog in dogs
                          where dog.Breed == DogBreed.GermanShepard
                          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, (s1, s2) => s1.Length + s2.Length);
' Filtering a list.
Dim germanShepards = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepard)

' Using the query syntax.
Dim queryGermanShepards = From dog In dogs
                          Where dog.Breed = DogBreed.GermanShepard
                          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(s1, s2) s1.Length + s2.Length)
  • リストをまとめてフラット化する場合:Flattening a list of lists:
// 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)
  • 2 つのセットの和集合 (カスタム比較子を含む):Union between two sets (with custom comparator):
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 つのセットの積集合:Intersection between two sets:
// 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())
  • 並べ替え:Ordering:
// 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 の投稿から借用し、変更したもの)。Finally, a more advanced sample: determining if the values of the properties of two instances of the same type are equal (Borrowed and modified from this StackOverflow post):
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

PLINQPLINQ

PLINQ (Parallel LINQ) は、LINQ 式の並列実行エンジンです。PLINQ, or Parallel LINQ, is a parallel execution engine for LINQ expressions. つまり、LINQ の正規表現は、任意の数のスレッドで普通に並列化できます。In other words, a regular LINQ expression can be trivially parallelized across any number of threads. これは、式の前に AsParallel() を指定して呼び出すことで実行できます。This is accomplished via a call to AsParallel() preceding the expression.

次の点を考慮します。Consider the following:

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 をパーティション分割し、各スレッドの "いいね!" の数を合計し、スレッドごとの計算結果を合計して、その結果を適切な文字列に投影します。This code will partition facebookUsers across system threads as necessary, sum up the total likes on each thread in parallel, sum the results computed by each thread, and project that result into a nice string.

図で表すと次のようになります。In diagram form:

PLINQ の図

LINQ で簡単に表すことができる (つまり、純粋関数で副作用のない) 並列化可能な CPU 制約のあるジョブは、PLINQ の候補として最適です。Parallelizable CPU-bound jobs which can be easily expressed via LINQ (in other words, are pure functions and have no side effects) are a great candidate for PLINQ. 副作用の_ある_ジョブの場合は、タスク並列ライブラリの使用を検討してください。For jobs which do have a side effect, consider using the Task Parallel Library.

他のリソース:Further Resources:

  • 101 個の LINQ サンプル101 LINQ Samples
  • Linqpad。プレイグラウンド環境とデータベース クエリ エンジン (C#/F#/Visual Basic 用)Linqpad, a playground environment and Database querying engine for C#/F#/Visual Basic
  • EduLinq。LINQ to Objects の実装方法を学習するための電子書籍EduLinq, an e-book for learning how LINQ-to-objects is implemented