LINQと同じ動作をするメソッドを実装することにより、具体的なコードを使って理解する。 またコードレベルでの動作を知ることにより適切な形でLINQを使うことができるようにする。

以下のサンプルでは簡単化のため引数チェックなどの処理は省略している。 また、ここではLINQのメソッド構文のみを扱い、クエリ構文は扱わない。

導入

Count

LINQのメソッドがどのような実装になっているかを理解するために、Countメソッドと同じ動作をするメソッドを実装することを考える。 まず、LINQのCountメソッドを使って要素数を取得する場合は次のようになる。

Countメソッド
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(source.Count());
  }
}
Countメソッド
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(source.Count())
  End Sub
End Class

これと同等の処理を行う方法のひとつとして次のようにする方法が考えられる。 つまり、単にforeachによりソースシーケンス(リスト・配列・コレクションや、LINQメソッドの戻り値として返されるIEnumerable<T>など)を列挙し、列挙できた要素を数え上げる。 (ICollection<T>ならばCountプロパティを参照することもできるが、ここではIEnumerable<T>に限定して考える)

IEnumerable<T>の要素数を数える方法
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    int count = 0;

    foreach (var e in source) {
      count++; // 列挙した回数=列挙できた要素数を計上する
    }

    Console.WriteLine(count);
  }
}
IEnumerable<T>の要素数を数える方法
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Dim count As Integer = 0

    For Each e In source
      count += 1 ' 列挙した回数=列挙できた要素数を計上する
    Next

    Console.WriteLine(count)
  End Sub
End Class

次に、このアルゴリズムを汎用的に使用できるようメソッド化すると次のようになる。

メソッド化した計上アルゴリズム
using System;
using System.Collections.Generic;

class Sample {
  static int Count(IEnumerable<int> source)
  {
    int count = 0;

    foreach (var e in source) {
      count++;
    }

    return count;
  }

  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(Count(source));
  }
}
メソッド化した計上アルゴリズム
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Class Sample
  Shared Function Count(ByVal source As IEnumerable(Of Integer)) As Integer
    Dim cnt As Integer = 0

    For Each e In source
      cnt += 1 ' 列挙した回数=列挙できた要素数を計上する
    Next

    Return cnt
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(Count(source))
  End Sub
End Class

これでは対象がint型のソースシーケンス(IEnumerable<int>)に限定されてしまうので、さらに汎用的に使用できるよう型指定をパラメータ化してジェネリックメソッドにし、任意の型のソースシーケンス(IEnumerable<TSource>)に対して使用できるようにする。

ジェネリックメソッド化した計上アルゴリズム
using System;
using System.Collections.Generic;

class Sample {
  static int Count<TSource>(IEnumerable<TSource> source)
  {
    int count = 0;

    foreach (var e in source) {
      count++;
    }

    return count;
  }

  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(Count(source));
  }
}
ジェネリックメソッド化した計上アルゴリズム
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Class Sample
  Shared Function Count(Of TSource)(ByVal source As IEnumerable(Of TSource)) As Integer
    Dim cnt As Integer = 0

    For Each e In source
      cnt += 1 ' 列挙した回数=列挙できた要素数を計上する
    Next

    Return cnt
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(Count(source))
  End Sub
End Class

さらに、LINQのCountメソッドと同様インスタンスメソッドのように呼び出せるようにするため、このメソッドを拡張メソッドにする。 (詳細:拡張メソッド) 拡張メソッドは静的クラス(static class, VBではモジュール)で実装する必要があるので、先のメソッドをクラス化する。 (ここではEnumerableというクラス名にする)

拡張メソッド化した計上アルゴリズム
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static int Count<TSource>(this IEnumerable<TSource> source)
  {
    int count = 0;

    foreach (var e in source) {
      count++;
    }

    return count;
  }
}

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(source.Count()); // インスタンスメソッドのように呼び出せる
  }
}
拡張メソッド化した計上アルゴリズム
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Function Count(Of TSource)(ByVal source As IEnumerable(Of TSource)) As Integer
    Dim cnt As Integer = 0

    For Each e In source
      cnt += 1
    Next

    Return cnt
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(source.Count()) ' インスタンスメソッドのように呼び出せる
  End Sub
End Class

これでLINQのCountメソッドと同等のメソッドを作ることができた。 ここまでの要点は、

  1. LINQのメソッドは任意の型のソースシーケンス(IEnumerable<TSource>)を対象としたアルゴリズム
  2. アルゴリズムを拡張メソッドとして実装することでインスタンスメソッドのように呼び出せる

First, Last

Firstメソッドも同様にして実装することができる。 Firstメソッドはソースシーケンスから最初の要素を取得して返す。 ソースシーケンスが空の場合は最初の要素を定義できないため、例外をスローする。

Firstメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static TSource First<TSource>(this IEnumerable<TSource> source)
  {
    foreach (var e in source) {
      return e; // 最初に列挙された要素を返す
    }

    // 列挙する要素がない=シーケンスが空の場合は例外をスローする
    throw new InvalidOperationException("ソースシーケンスが空です");
  }
}

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(source.First());
  }
}
Firstメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Function First(Of TSource)(ByVal source As IEnumerable(Of TSource)) As TSource
    For Each e In source
      Return e ' 最初に列挙された要素を返す
    Next

    ' 列挙する要素がない=シーケンスが空の場合は例外をスローする
    Throw New InvalidOperationException("ソースシーケンスが空です")
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(source.First())
  End Sub
End Class
実行結果
0

一方FirstOrDefaultメソッドでは、ソースシーケンスが空の場合にはデフォルトの値を返すとされている。 これを実装すると次のようになる。

FirstOrDefaultメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source)
  {
    foreach (var e in source) {
      return e; // 最初に列挙された要素を返す
    }

    // 列挙する要素がない=シーケンスが空の場合はデフォルトの値を返す
    return default(TSource);
  }
}

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {2, 3, 4};

    Console.WriteLine(source.FirstOrDefault());

    IEnumerable<int> empty = new List<int>();

    Console.WriteLine(empty.FirstOrDefault());
  }
}
FirstOrDefaultメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Function FirstOrDefault(Of TSource)(ByVal source As IEnumerable(Of TSource)) As TSource
    For Each e In source
      Return e ' 最初に列挙された要素を返す
    Next

    ' 列挙する要素がない=シーケンスが空の場合はデフォルトの値を返す
    Return Nothing
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {2, 3, 4}

    Console.WriteLine(source.FirstOrDefault())

    Dim empty As IEnumerable(Of Integer) = New List(Of Integer)()

    Console.WriteLine(empty.FirstOrDefault())
  End Sub
End Class
実行結果
2
0

Firstメソッドでは例外をスローしていた箇所を、FirstOrDefaultメソッドではデフォルト値を返すように置き換えている。 ジェネリックメソッドで型に応じたデフォルト値を返すにはdefault()(VBではNothing)を使用する。 (型の種類・サイズ・精度・値域 §.型のデフォルト値)

LastメソッドおよびLastOrDefaultメソッドも、default(またはNothing)を使うことで同様に実装できる。

Lastメソッド・LastOrDefaultメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static TSource Last<TSource>(this IEnumerable<TSource> source)
  {
    TSource last = default(TSource);
    bool isEmpty = true;

    foreach (var e in source) {
      last = e; // 最後に列挙された要素を保持する
      isEmpty = false; // ひとつでも列挙されたらfalse(=空ではない)にする
    }

    if (isEmpty)
      // 要素が1つも列挙されなかった場合
      throw new InvalidOperationException("ソースシーケンスが空です");
    else
      // 列挙された場合は最後に列挙された要素を返す
      return last;
  }

  public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source)
  {
    TSource last = default(TSource);

    foreach (var e in source) {
      last = e; // 最後に列挙された要素を保持する
    }

    // 最後に列挙された要素、またはデフォルトの値を返す
    return last;
  }
}

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(source.Last());

    IEnumerable<int> empty = new List<int>();

    Console.WriteLine(empty.LastOrDefault());
  }
}
Lastメソッド・LastOrDefaultメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Function Last(Of TSource)(ByVal source As IEnumerable(Of TSource)) As TSource
    Dim lastItem As TSource = Nothing
    Dim isEmpty As Boolean = True

    For Each e In source
      lastItem = e ' 最後に列挙された要素を保持する
      isEmpty = False ' ひとつでも列挙されたらfalse(=空ではない)にする
    Next

    If isEmpty Then
      ' 要素が1つも列挙されなかった場合
      Throw New InvalidOperationException("ソースシーケンスが空です")
    Else
      ' 列挙された場合は最後に列挙された要素を返す
      Return lastItem
    End If
  End Function

  <System.Runtime.CompilerServices.Extension()> _
  Public Function LastOrDefault(Of TSource)(ByVal source As IEnumerable(Of TSource)) As TSource
    Dim lastItem As TSource = Nothing

    For Each e In source
      lastItem = e ' 最後に列挙された要素を保持する
    Next

    ' 最後に列挙された要素、またはデフォルトの値を返す
    Return lastItem
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(source.Last())

    Dim empty As IEnumerable(Of Integer) = New List(Of Integer)()

    Console.WriteLine(empty.LastOrDefault())
  End Sub
End Class

Skip, Take

Skipメソッドはソースシーケンスから最初のn個の要素を除いた部分(スキップした部分)を取り出す。 逆にTakeメソッドはソースシーケンスから最初のn個の要素のみを取り出す。

Skipメソッド・Takeメソッド
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    var source = new List<int>() {0, 1, 2, 3, 4};

    // 先頭から3つの要素を除いた残りの要素を取り出して表示する
    Console.WriteLine(string.Join(", ", source.Skip(3)));

    // 先頭から3つの要素を取り出して表示する
    Console.WriteLine(string.Join(", ", source.Take(3)));
  }
}
Skipメソッド・Takeメソッド
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    ' 先頭から3つの要素を除いた残りの要素を取り出して表示する
    Console.WriteLine(String.Join(", ", source.Skip(3)))

    ' 先頭から3つの要素を取り出して表示する
    Console.WriteLine(String.Join(", ", source.Take(3)))
  End Sub
End Class
実行結果
3, 4
0, 1, 2

これを実装すると次のようになる。

Skipメソッド・Takeメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)
  {
    var result = new List<TSource>(); // 結果を格納するリスト

    foreach (var e in source) {
      if (0 < count)
        count--;
      else
        result.Add(e); // 指定された要素数を列挙した時点から追加する
    }

    return result;
  }

  public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
  {
    var result = new List<TSource>(); // 結果を格納するリスト

    foreach (var e in source) {
      if (0 < count)
        result.Add(e); // 指定された要素数となるまで追加する
      else
        break; // 指定された要素数を超えたら列挙を中断してその時点までの結果を返す

      count--;
    }

    return result;
  }
}

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", source.Take(3)));

    Console.WriteLine(string.Join(", ", source.Skip(3)));
  }
}
Skipメソッド・Takeメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Function Skip(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal count As Integer) As IEnumerable(Of TSource)
    Dim result As New List(Of TSource)() ' 結果を格納するリスト

    For Each e In source
      If 0 < count Then
        count -= 1
      Else
        result.Add(e) ' 指定された要素数を列挙した時点から追加する
      End If
    Next

    Return result
  End Function

  <System.Runtime.CompilerServices.Extension()> _
  Public Function Take(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal count As Integer) As IEnumerable(Of TSource)
    Dim result As New List(Of TSource)() ' 結果を格納するリスト

    For Each e In source
      If 0 < count Then
        result.Add(e) ' 指定された要素数となるまで追加する
      Else
        Exit For ' 指定された要素数を超えたら列挙を中断してその時点までの結果を返す
      End If

      count -= 1
    Next

    Return result
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    ' 先頭から3つの要素を除いた残りの要素を取り出して表示する
    Console.WriteLine(String.Join(", ", source.Skip(3)))

    ' 先頭から3つの要素を取り出して表示する
    Console.WriteLine(String.Join(", ", source.Take(3)))
  End Sub
End Class

ここで、Skipメソッド・Takeメソッドのドキュメントでは以下のように記述されている。

このメソッドは遅延実行を使用して実装されます。アクションの実行に必要なすべての情報を格納するオブジェクトがすぐに返されます。このメソッドによって表されるクエリは、オブジェクトの GetEnumerator メソッドを直接呼び出すか、foreach (Visual C# の場合) または For Each (Visual Basic の場合) を使用することによってオブジェクトが列挙されるまで実行されません。

Enumerable.Take<TSource> メソッド (IEnumerable<TSource>, Int32)

これはSkipメソッド・Takeメソッドに限らずIEnumerable<T>を返すLINQメソッドに共通するものとなっている。

IEnumerable<T>の列挙結果をyieldを使って生成するメソッドでは遅延実行が行われる。 先の実装では結果をList<TSource>に格納して返したが、かわりにyieldによって逐次結果を返すようにする。 (VBではさらにメソッドに修飾子Iteratorを付ける)

yieldを使って遅延実行するようにする
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> source, int count)
  {
    foreach (var e in source) {
      if (0 < count)
        count--;
      else
        yield return e; // yieldによって結果を返す
    }
  }

  public static IEnumerable<TSource> Take<TSource>(this IEnumerable<TSource> source, int count)
  {
    foreach (var e in source) {
      if (0 < count)
        yield return e; // yieldによって結果を返す
      else
        break;

      count--;
    }
  }
}

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", source.Skip(3)));

    Console.WriteLine(string.Join(", ", source.Take(3)));
  }
}
yieldを使って遅延実行するようにする
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Iterator Function Skip(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal count As Integer) As IEnumerable(Of TSource)
    For Each e In source
      If 0 < count Then
        count -= 1
      Else
        Yield e ' Yieldによって結果を返す
      End If
    Next
  End Function

  <System.Runtime.CompilerServices.Extension()> _
  Public Iterator Function Take(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal count As Integer) As IEnumerable(Of TSource)
    For Each e In source
      If 0 < count Then
        Yield e ' yieldによって結果を返す
      Else
        Exit For
      End If

      count -= 1
    Next
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(String.Join(", ", source.Skip(3)))

    Console.WriteLine(String.Join(", ", source.Take(3)))
  End Sub
End Class

これにより動作は変わらず、遅延実行するようになる。

実行結果
3, 4
0, 1, 2

ここまでの要点は

  1. Skip()などのソースシーケンスに加工して新たなシーケンス(IEnumerable<T>)を生み出すLINQメソッドでは、遅延実行が行われる
  2. 遅延実行yieldによって実装することができる

述語(predicate)を引数にとるメソッド

Where

Whereメソッドは指定された条件(あるいは述語、predicate)に基づいてシーケンス内の要素をフィルタして新たなシーケンスを生成することができる。 例えば偶数だけをフィルタするには次のようにする。

Whereメソッドを使ったシーケンスのフィルタ
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", source.Where(val => val % 2 == 0))); // 偶数(=2で割った余りが0)の要素だけをフィルタする
  }
}
Whereメソッドを使ったシーケンスのフィルタ
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(String.Join(", ", source.Where(Function(val) val Mod 2 = 0))) ' 偶数(=2で割った余りが0)の要素だけをフィルタする
  End Sub
End Class
実行結果
0, 2, 4

フィルタの条件は、TSource型の要素を検査してboolを返すメソッドのデリゲート(Func<TSource, bool>)を引数predicateに渡すことによって指定する。 ラムダ式を使わずに条件を記述するとこのようになる。 ここでは要素の型TSourceintなので、predicateにはデリゲートFunc<int, bool>を渡す。

フィルタの条件を定義したメソッド
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  // 値が偶数か否かを判定するメソッド
  static bool IsEven(int val)
  {
    if (val % 2 == 0)
      return true;
    else
      return false;
  }
  
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    var predicate = (Func<int, bool>)IsEven; // フィルタの条件

    Console.WriteLine(string.Join(", ", source.Where(predicate)));
  }
}
フィルタの条件を定義したメソッド
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  ' 値が偶数か否かを判定するメソッド
  Shared Function IsEven(ByVal val As Integer) As Boolean
    If val Mod 2 = 0 Then
      Return True
    Else
      Return False
    End If
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Dim predicate As Func(Of Integer, Boolean) = AddressOf IsEven ' フィルタの条件

    Console.WriteLine(String.Join(", ", source.Where(predicate)))
  End Sub
End Class

これをLINQを使わずに記述すると次のようになる。

LINQを使わずにソースシーケンスをフィルタする
using System;
using System.Collections.Generic;

class Sample {
  // 値が偶数か否かを判定するメソッド
  static bool IsEven(int val)
  {
    if (val % 2 == 0)
      return true;
    else
      return false;
  }
  
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    var predicate = (Func<int, bool>)IsEven; // フィルタの条件

    var result = new List<int>(); // フィルタした結果を代入するリスト

    foreach (var e in source) {
      if (predicate(e))
        result.Add(e); // 条件に一致する要素のみをリストに格納する
    }

    Console.WriteLine(string.Join(", ", result));
  }
}
LINQを使わずにソースシーケンスをフィルタする
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Class Sample
  ' 値が偶数か否かを判定するメソッド
  Shared Function IsEven(ByVal val As Integer) As Boolean
    If val Mod 2 = 0 Then
      Return True
    Else
      Return False
    End If
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Dim predicate As Func(Of Integer, Boolean) = AddressOf IsEven ' フィルタの条件

    Dim result As New List(Of Integer)() ' フィルタした結果を代入するリスト

    For Each e In source
      If predicate(e) Then
        result.Add(e) ' 条件に一致する要素のみをリストに格納する
      End If
    Next

    Console.WriteLine(String.Join(", ", result))
  End Sub
End Class

これまでの例と同様、このアルゴリズムを遅延実行するようにし、かつ拡張メソッドとして呼び出せるようにするとこのようになる。

Whereメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
  {
    foreach (var e in source) {
      if (predicate(e))
        yield return e; // 条件に一致する要素のみをyieldによって返す
    }
  }
}

class Sample {
  // 値が偶数か否かを判定するメソッド
  static bool IsEven(int val)
  {
    if (val % 2 == 0)
      return true;
    else
      return false;
  }
  
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", source.Where(IsEven)));

    // 当然、ラムダ式を使って呼び出すこともできる
    Console.WriteLine(string.Join(", ", source.Where(val => val % 2 == 0)));
  }
}
Whereメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Iterator Function Where(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal predicate As Func(Of TSource, Boolean)) As IEnumerable(Of TSource)
    For Each e In source
      If predicate(e) Then
        Yield e ' 条件に一致する要素のみをYieldによって返す
      End If
    Next
  End Function
End Module

Class Sample
  ' 値が偶数か否かを判定するメソッド
  Shared Function IsEven(ByVal val As Integer) As Boolean
    If val Mod 2 = 0 Then
      Return True
    Else
      Return False
    End If
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(String.Join(", ", source.Where(AddressOf IsEven)))

    ' 当然、ラムダ式を使って呼び出すこともできる
    Console.WriteLine(String.Join(", ", source.Where(Function(val) val Mod 2 = 0)))
  End Sub
End Class

All, Any

Allメソッドはソースシーケンス内のすべての要素が条件を満たすかどうかを調べる。 またAnyメソッドは条件を満たす要素が一つでもあるかどうかを調べる。

Allメソッド・Anyメソッド
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  // 値が偶数か否かを判定するメソッド
  static bool IsEven(int val)
  {
    if (val % 2 == 0)
      return true;
    else
      return false;
  }
  
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(source.All(IsEven)); // すべての要素が偶数かどうか
    Console.WriteLine(source.Any(IsEven)); // 偶数の要素を含むかどうか
  }
}
Allメソッド・Anyメソッド
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  ' 値が偶数か否かを判定するメソッド
  Shared Function IsEven(ByVal val As Integer) As Boolean
    If val Mod 2 = 0 Then
      Return True
    Else
      Return False
    End If
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(source.All(AddressOf IsEven)) ' すべての要素が偶数かどうか
    Console.WriteLine(source.Any(AddressOf IsEven)) ' 偶数の要素を含むかどうか
  End Sub
End Class
実行結果
False
True

これもWhereメソッドの場合と同様にFunc<TSource, bool>を使って次のように実装することができる。

Allメソッド・Anyメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
  {
    foreach (var e in source) {
      if (!predicate(e))
        return false; // 条件に一致しない要素があった時点でfalseを返す
    }

    return true; // すべて条件に一致する場合はtrueを返す
  }

  public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
  {
    foreach (var e in source) {
      if (predicate(e))
        return true; // 条件に一致する要素があった場合はtrueを返す
    }

    return false; // なければfalseを返す
  }
}

class Sample {
  // 値が偶数か否かを判定するメソッド
  static bool IsEven(int val)
  {
    if (val % 2 == 0)
      return true;
    else
      return false;
  }
  
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(source.All(IsEven)); // すべての要素が偶数かどうか
    Console.WriteLine(source.Any(IsEven)); // 偶数の要素を含むかどうか
  }
}
Allメソッド・Anyメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Function All(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal predicate As Func(Of TSource, Boolean)) As Boolean
    For Each e In source
      If Not predicate(e) Then
        Return False ' 条件に一致しない要素があった時点でFalseを返す
      End If
    Next

    Return True ' すべて条件に一致する場合はTrueを返す
  End Function

  <System.Runtime.CompilerServices.Extension()> _
  Public Function Any(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal predicate As Func(Of TSource, Boolean)) As Boolean
    For Each e In source
      If predicate(e) Then
        Return True ' 条件に一致する要素があった場合はTrueを返す
      End If
    Next

    Return False ' なければFalseを返す
  End Function
End Module

Class Sample
  ' 値が偶数か否かを判定するメソッド
  Shared Function IsEven(ByVal val As Integer) As Boolean
    If val Mod 2 = 0 Then
      Return True
    Else
      Return False
    End If
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(source.All(AddressOf IsEven)) ' すべての要素が偶数かどうか
    Console.WriteLine(source.Any(AddressOf IsEven)) ' 偶数の要素を含むかどうか
  End Sub
End Class

SkipWhile, TakeWhile

要素数を指定するSkipメソッド・Takeメソッドとは異なり、SkipWhileメソッドTakeWhileメソッドでは要素を選別する条件をFunc<TSource, bool>で指定することができる。

SkipWhileメソッド・TakeWhileメソッド
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", source.SkipWhile(val => val <= 2))); // 要素が2以下である間はスキップする
    Console.WriteLine(string.Join(", ", source.TakeWhile(val => val <= 2))); // 要素が2以下である間は要素を抽出する
  }
}
SkipWhileメソッド・TakeWhileメソッド
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(String.Join(", ", source.SkipWhile(Function(val) val <= 2))) ' 要素が2以下である間はスキップする
    Console.WriteLine(String.Join(", ", source.TakeWhile(Function(val) val <= 2))) ' 要素が2以下である間は要素を抽出する
  End Sub
End Class
実行結果
3, 4
0, 1, 2

これもAllメソッド・Anyメソッドと同様に次のように実装できる。

SkipWhileメソッド・TakeWhileメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static IEnumerable<TSource> TakeWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
  {
    foreach (var e in source) {
      if (predicate(e))
        yield return e; // 要素が条件に一致する場合はその要素を返す
      else
        break; // 一致しなくなった時点で列挙を中断する
    }
  }

  public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
  {
    bool skip = true;

    foreach (var e in source) {
      if (skip) {
        if (!predicate(e)) {
          yield return e; // 要素が条件に一致しなくなった時点でその要素を返す
          skip = false; // 以降の要素をすべて返す
        }
      }
      else {
        yield return e;
      }
    }
  }
}

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", source.SkipWhile(val => val <= 2))); // 要素が2以下である間はスキップする
    Console.WriteLine(string.Join(", ", source.TakeWhile(val => val <= 2))); // 要素が2以下である間は要素を抽出する
  }
}
SkipWhileメソッド・TakeWhileメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Iterator Function TakeWhile(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal predicate As Func(Of TSource, Boolean)) As IEnumerable(Of TSource)
    For Each e In source
      If predicate(e) Then
        Yield e ' 要素が条件に一致する場合はその要素を返す
      Else
        Exit For ' 一致しなくなった時点で列挙を中断する
      End If
    Next
  End Function

  <System.Runtime.CompilerServices.Extension()> _
  Public Iterator Function SkipWhile(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal predicate As Func(Of TSource, Boolean)) As IEnumerable(Of TSource)
    Dim skip As Boolean = True

    For Each e In source
      If skip Then
        If Not predicate(e) Then
          Yield e ' 要素が条件に一致しなくなった時点でその要素を返す
          skip = False ' 以降の要素をすべて返す
        End If
      Else
        Yield e
      End If
    Next
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(String.Join(", ", source.SkipWhile(Function(val) val <= 2))) ' 要素が2以下である間はスキップする
    Console.WriteLine(String.Join(", ", source.TakeWhile(Function(val) val <= 2))) ' 要素が2以下である間は要素を抽出する
  End Sub
End Class

実行結果
3, 4
0, 1, 2

変換関数(selector)を引数にとるメソッド

Select

Selectメソッドは指定された変換関数(Func<TSource, TResult>)に基づいてシーケンス内の全要素を変換し、新しいシーケンスを返す。 変換関数(Convert<TInput, TOutput>)を使って配列の型を変換するArray.ConvertAllメソッドと似ている。 (配列操作 §.全要素の変換 (ConvertAll))

次の例では、クラスから特定のフィールドを選択する変換関数を指定することにより、クラス型のシーケンスから各フィールドの値を抽出したシーケンスを生成している。

Selectメソッド
using System;
using System.Collections.Generic;
using System.Linq;

class Account {
  public int ID;
  public string Name;

  public override string ToString()
  {
    return string.Format("{0}:{1}", ID, Name);
  }
}

class Sample {
  static void Main()
  {
    IEnumerable<Account> source = new List<Account>() {
      new Account() {ID = 0, Name = "Alice"},
      new Account() {ID = 1, Name = "Bob"},
      new Account() {ID = 2, Name = "Charlie"},
    };

    Console.WriteLine(string.Join(", ", source));

    // シーケンス内の各要素からNameプロパティの値だけを'選択'する
    var names = source.Select(account => account.Name);

    Console.WriteLine(string.Join(", ", names));

    // シーケンス内の各要素からIDプロパティの値だけを'選択'する
    var ids = source.Select(account => account.ID);

    Console.WriteLine(string.Join(", ", ids));
  }
}
Selectメソッド
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Account
  Public ID As Integer
  Public Name As String

  Public Overrides Function ToString() As String
    Return String.Format("{0}:{1}", ID, Name)
  End Function
End Class

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Account) = New List(Of Account) From { _
      New Account With {.ID = 0, .Name = "Alice"}, _
      New Account With {.ID = 1, .Name = "Bob"}, _
      New Account With {.ID = 2, .Name = "Charlie"} _
    }

    Console.WriteLine(String.Join(", ", source))

    ' シーケンス内の各要素からNameプロパティの値だけを'選択'する
    Dim names = source.Select(Function(account) account.Name)

    Console.WriteLine(String.Join(", ", names))

    ' シーケンス内の各要素からIDプロパティの値だけを'選択'する
    Dim ids = source.Select(Function(account) account.ID)

    Console.WriteLine(String.Join(", ", ids))
  End Sub
End Class
実行結果
0:Alice, 1:Bob, 2:Charlie
Alice, Bob, Charlie
0, 1, 2

もうひとつ別の例として、シーケンス内の全要素の平方根を求めるには次のようにする。

Selectメソッドを使ってシーケンスの平方根を求める例
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    Console.WriteLine(string.Join(", ", source.Select(val => Math.Sqrt(val))));
  }
}
Selectメソッドを使ってシーケンスの平方根を求める例
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Console.WriteLine(String.Join(", ", source.Select(Function(val) Math.Sqrt(val))))
  End Sub
End Class
実行結果
0, 1, 1.4142135623731, 1.73205080756888, 2

Selectメソッドでは、変換関数(Func<TSource, TResult>)を引数selectorで指定する。 変換関数の引数の型は元のシーケンスと同じ型(TSource)で、戻り値は変換後の型(TResult)となる。 ラムダ式を使わずに変換関数を記述するとこのようになる。

ラムダ式を使わずに変換関数を記述した例
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  // 与えられた値をその平方根に変換するメソッド
  static double Sqrt(int val)
  {
    return Math.Sqrt(val);
  }

  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    var selector = (Func<int, double>)Sqrt; // intの値をdoubleに変換する変換関数

    Console.WriteLine(string.Join(", ", source.Select(selector)));
  }
}
ラムダ式を使わずに変換関数を記述した例
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  ' 与えられた値をその平方根に変換するメソッド
  Shared Function Sqrt(ByVal val As Integer) As Double
    Return Math.Sqrt(val)
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Dim selector As Func(Of Integer, Double) = AddressOf Sqrt ' Integerの値をDoubleに変換する変換関数

    Console.WriteLine(String.Join(", ", source.Select(selector)))
  End Sub
End Class

これをLINQを使わずに記述すると次のようになる。

LINQを使わずにソースシーケンスを変換する例
using System;
using System.Collections.Generic;

class Sample {
  // 与えられた値をその平方根に変換するメソッド
  static double Sqrt(int val)
  {
    return Math.Sqrt(val);
  }

  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    var selector = (Func<int, double>)Sqrt; // 変換関数

    var result = new List<double>(); // 変換した結果を代入するリスト

    foreach (var e in source) {
      result.Add(selector(e)); // 列挙された要素に変換関数を適用してリストに追加する
    }

    Console.WriteLine(string.Join(", ", result));
  }
}
LINQを使わずにソースシーケンスを変換する例
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Class Sample
  ' 与えられた値をその平方根に変換するメソッド
  Shared Function Sqrt(ByVal val As Integer) As Double
    Return Math.Sqrt(val)
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Dim selector As Func(Of Integer, Double) = AddressOf Sqrt ' 変換関数

    Dim result As New List(Of Double)() ' 変換した結果を代入するリスト

    For Each e In source
      result.Add(selector(e)) ' 列挙された要素に変換関数を適用してリストに追加する
    Next

    Console.WriteLine(String.Join(", ", result))
  End Sub
End Class

これまでの例と同様、このアルゴリズムを遅延実行するようにし、かつ拡張メソッドとして呼び出せるようにするとこのようになる。

Selectメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
  {
    foreach (var e in source) {
      yield return selector(e); // 変換関数を適用した結果をyieldによって返す
    }
  }
}

class Sample {
  // 与えられた値をその平方根に変換するメソッド
  static double Sqrt(int val)
  {
    return Math.Sqrt(val);
  }

  static void Main()
  {
    IEnumerable<int> source = new List<int>() {0, 1, 2, 3, 4};

    var selector = (Func<int, double>)Sqrt; // 変換関数

    Console.WriteLine(string.Join(", ", source.Select(selector)));

    // 当然、ラムダ式を使って呼び出すこともできる
    Console.WriteLine(string.Join(", ", source.Select(val => Math.Sqrt(val))));
  }
}
Selectメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Iterator Function [Select](Of TSource, TResult)(ByVal source As IEnumerable(Of TSource), ByVal selector As Func(Of TSource, TResult)) As IEnumerable(Of TResult)
    For Each e In source
      Yield selector(e) ' 変換関数を適用した結果をYieldによって返す
    Next
  End Function
End Module

Class Sample
  ' 与えられた値をその平方根に変換するメソッド
  Shared Function Sqrt(ByVal val As Integer) As Double
    Return Math.Sqrt(val)
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of Integer) = New List(Of Integer) From {0, 1, 2, 3, 4}

    Dim selector As Func(Of Integer, Double) = AddressOf Sqrt ' 変換関数

    Console.WriteLine(String.Join(", ", source.Select(selector)))

    ' 当然、ラムダ式を使って呼び出すこともできる
    Console.WriteLine(String.Join(", ", source.Select(Function(val) Math.Sqrt(val))))
  End Sub
End Class

Average, Sum

AverageメソッドSumメソッドは変換関数によって平均・合計として計上する値を各要素から選択する。 これによりシーケンス内の全要素から特定の値に基づいた平均・合計を求めることができる。

Averageメソッド・Sumメソッド
using System;
using System.Collections.Generic;
using System.Linq;

class File {
  public string Name;
  public int Length;
}

class Sample {
  static void Main()
  {
    IEnumerable<File> source = new List<File>() {
      new File() {Name = "sample.txt", Length = 123},
      new File() {Name = "sample.jpg", Length = 45679},
      new File() {Name = "sample.zip", Length = 3145},
    };

    Console.WriteLine("Average = {0}", source.Average(file => file.Length)); // Lengthの平均を求める

    Console.WriteLine("Sum = {0}", source.Sum(file => file.Length)); // Lengthの合計を求める
  }
}
Averageメソッド・Sumメソッド
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic
Imports System.Linq

Class File
  Public Name As String
  Public Length As Integer
End Class

Class Sample
  Shared Sub Main()
    Dim source As IEnumerable(Of File) = New List(Of File) From { _
      New File() With {.Name = "sample.txt", .Length = 123}, _
      New File() With {.Name = "sample.jpg", .Length = 45679}, _
      New File() With {.Name = "sample.zip", .Length = 3145} _
    }

    Console.WriteLine("Average = {0}", source.Average(Function(file) file.Length)) ' Lengthの平均を求める

    Console.WriteLine("Sum = {0}", source.Sum(Function(file) file.Length)) ' Lengthの合計を求める
  End Sub
End Class
実行結果
Average = 16315.6666666667
Sum = 48947

これをLINQを使わずに記述すると次のようになる。

LINQを使わずに平均・合計を求める例
using System;
using System.Collections.Generic;

class File {
  public string Name;
  public int Length;
}

class Sample {
  // FileクラスのLengthフィールドの値を参照・取得するメソッド
  static int GetLength(File file)
  {
    return file.Length;
  }

  static void Main()
  {
    IEnumerable<File> source = new List<File>() {
      new File() {Name = "sample.txt", Length = 123},
      new File() {Name = "sample.jpg", Length = 45679},
      new File() {Name = "sample.zip", Length = 3145},
    };

    var selector = (Func<File, int>)GetLength; // 合計を求めるための値を選択するための変換関数

    int sum = 0;
    int count = 0;

    foreach (var e in source) {
      sum += selector(e); // 要素から合計として加算する値を取得する
      count++;
    }

    Console.WriteLine("Average = {0}", sum / (double)count);

    Console.WriteLine("Sum = {0}", sum);
  }
}
LINQを使わずに平均・合計を求める例
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Class File
  Public Name As String
  Public Length As Integer
End Class

Class Sample
  ' FileクラスのLengthフィールドの値を参照・取得するメソッド
  Shared Function GetLength(ByVal file As File) As Integer
    Return file.Length
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of File) = New List(Of File) From { _
      New File() With {.Name = "sample.txt", .Length = 123}, _
      New File() With {.Name = "sample.jpg", .Length = 45679}, _
      New File() With {.Name = "sample.zip", .Length = 3145} _
    }

    Dim selector As Func(Of File, Integer) = AddressOf GetLength ' 合計を求めるための値を選択するための変換関数

    Dim sum As Integer = 0
    Dim count As Integer = 0

    For Each e In source
      sum += selector(e)
      count += 1
    Next

    Console.WriteLine("Average = {0}", sum / CDbl(count)) ' Lengthの平均を求める

    Console.WriteLine("Sum = {0}", sum) ' Lengthの合計を求める
  End Sub
End Class

これまでの例と同様、このアルゴリズムを遅延実行するようにし、かつ拡張メソッドとして呼び出せるようにするとこのようになる。

Averageメソッド・Sumメソッドの実装
using System;
using System.Collections.Generic;

public static class Enumerable {
  public static double Average<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
  {
    int sum = 0;
    int count = 0;

    foreach (var e in source) {
      sum += selector(e);
      count++;
    }

    if (count == 0)
      throw new InvalidOperationException("ソースシーケンスが空です");
    else
      return sum / (double)count;
  }

  public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
  {
    int sum = 0;

    foreach (var e in source) {
      sum += selector(e);
    }

    return sum;
  }
}

class File {
  public string Name;
  public int Length;
}

class Sample {
  // FileクラスのLengthフィールドの値を参照・取得するメソッド
  static int GetLength(File file)
  {
    return file.Length;
  }

  static void Main()
  {
    IEnumerable<File> source = new List<File>() {
      new File() {Name = "sample.txt", Length = 123},
      new File() {Name = "sample.jpg", Length = 45679},
      new File() {Name = "sample.zip", Length = 3145},
    };

    var selector = (Func<File, int>)GetLength; // 合計を求めるための値を選択するための変換関数

    Console.WriteLine("Average = {0}", source.Average(selector)); // Lengthの平均を求める

    Console.WriteLine("Sum = {0}", source.Sum(selector)); // Lengthの合計を求める

    // 当然、ラムダ式を使って呼び出すこともできる
    Console.WriteLine("Average = {0}", source.Average(file => file.Length));

    Console.WriteLine("Sum = {0}", source.Sum(file => file.Length));
  }
}
Averageメソッド・Sumメソッドの実装
Option Strict On
Option Infer On

Imports System
Imports System.Collections.Generic

Public Module Enumerable
  <System.Runtime.CompilerServices.Extension()> _
  Public Function Average(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal selector As Func(Of TSource, Integer)) As Double
    Dim sum As Integer = 0
    Dim count As Integer = 0

    For Each e In source
      sum += selector(e)
      count += 1
    Next

    If count = 0 Then
      Throw New InvalidOperationException("ソースシーケンスが空です")
    Else
      Return sum / CDbl(count)
    End If
  End Function

  <System.Runtime.CompilerServices.Extension()> _
  Public Function Sum(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal selector As Func(Of TSource, Integer)) As Integer
    Dim s As Integer = 0

    For Each e In source
      s += selector(e)
    Next

    Return s
  End Function
End Module

Class File
  Public Name As String
  Public Length As Integer
End Class

Class Sample
  ' FileクラスのLengthフィールドの値を参照・取得するメソッド
  Shared Function GetLength(ByVal file As File) As Integer
    Return file.Length
  End Function

  Shared Sub Main()
    Dim source As IEnumerable(Of File) = New List(Of File) From { _
      New File() With {.Name = "sample.txt", .Length = 123}, _
      New File() With {.Name = "sample.jpg", .Length = 45679}, _
      New File() With {.Name = "sample.zip", .Length = 3145} _
    }

    Dim selector As Func(Of File, Integer) = AddressOf GetLength ' 合計を求めるための値を選択するための変換関数

    Console.WriteLine("Average = {0}", source.Average(selector)) ' Lengthの平均を求める

    Console.WriteLine("Sum = {0}", source.Sum(selector)) ' Lengthの合計を求める

    ' 当然、ラムダ式を使って呼び出すこともできる
    Console.WriteLine("Average = {0}", source.Average(Function(file) file.Length))

    Console.WriteLine("Sum = {0}", source.Sum(Function(file) file.Length))
  End Sub
End Class