いろいろ備忘録日記

主に .NET と Python絡みのメモを公開しています。

Linq入門記-16 (LINQ To Object, 変換演算子, OfType)


前回に引き続き、変換演算子についてです。
今回は、OfTypeメソッドです。


OfTypeメソッドは、対象となるシーケンスを特定の型のみのシーケンスに
変換するメソッドです。


OfTypeメソッドの書式は、以下のようになっています。

public static IEnumerable<TResult> OfType<TResult>(
    this IEnumerable source
)

IEnumerableの拡張メソッドではなく、IEnumerableの拡張メソッドとして
定義されています。これにより、このメソッドは、Genericではないシーケンスに対しても
利用出来ます。


例えば、基底クラスとしてPersonクラスが存在し
派生クラスとしてCustomerクラスが存在するとします。
そして、Listというリストの中に、Personオブジェクトと
Customerオブジェクトの両方が設定されている状態で

persons.OfType<Customer>();

とすると、Customerオブジェクトのみのリストが取得できます。


よく利用するのが、GenericではないArrayListなどをIEnumerableに変換する際です。

//
// 元々GenericではないリストをIEnumerable<T>に変換する場合にも利用出来る.
//
ArrayList arrayList = new ArrayList();
arrayList.Add(10);
arrayList.Add(20);
arrayList.Add(30);
arrayList.Add(40);

IEnumerable<int> intList = arrayList.OfType<int>();
foreach (var data in intList)
{
    Console.WriteLine(data);
}

以下、サンプルです。

    #region LinqSamples-16
    public class LinqSamples16 : IExecutable
    {
        class Person
        {
            public int    Id   { get; set; }
            public string Name { get; set; }
        }
        
        class Customer : Person
        {
            public IEnumerable<Order> Orders { get; set; }
        }
        
        class Order
        {
            public int Id       { get; set; }
            public int Quantity { get; set; }
        }
        
        public void Execute()
        {
            List<Person> persons = new List<Person>
            {
                 new Person { Id = 1, Name = "gsf_zero1" }
                ,new Person { Id = 2, Name = "gsf_zero2" }
                ,new Customer { Id = 3, Name = "gsf_zero3", Orders = Enumerable.Empty<Order>() }
                ,new Customer 
                     { 
                         Id = 4
                        ,Name = "gsf_zero4"
                        ,Orders =  new List<Order>
                             {
                                  new Order { Id = 1, Quantity = 10 }
                                 ,new Order { Id = 2, Quantity = 2  }
                             }
                     }
                ,new Person { Id = 5, Name = "gsf_zero5" }
            };

            //
            // OfTypeメソッドを利用することにより、特定の型のみのシーケンスに変換することができる。
            // 例えば、リストの中に基底クラスであるPersonクラスと派生クラスであるCustomerクラスの
            // オブジェクトが混在している場合、OfType<Person>とすると、そのまま。
            // OfType<Customer>とすると、Customerオブジェクトのみのシーケンスに変換される。
            //
            //
            // 尚、OfTypeメソッドは他の変換演算子とは違い、ソースシーケンスのスナップショットを作成しない。
            // つまり、通常のクエリと同じく、OfTypeで取得したシーケンスが列挙される度に評価される。
            // 変換演算子の中で、このような動作を行うのはAsEnumerableとOfTypeとCastとなる。
            //
            Console.WriteLine("========== OfType<Person>の結果 ==========");
            foreach (var data in persons.OfType<Person>())
            {
                Console.WriteLine(data);
            }
            
            Console.WriteLine("========== OfType<Customer>の結果 ==========");
            foreach (var data in persons.OfType<Customer>())
            {
                Console.WriteLine(data);
            }
            
            //
            // 元々GenericではないリストをIEnumerable<T>に変換する場合にも利用出来る.
            //
            ArrayList arrayList = new ArrayList();
            arrayList.Add(10);
            arrayList.Add(20);
            arrayList.Add(30);
            arrayList.Add(40);
            
            Console.WriteLine("========== Genericではないコレクションを変換 ==========");
            foreach (var data in arrayList.OfType<int>())
            {
                Console.WriteLine(data);
            }
        }
    }
    #endregion


結果は以下のようになります。

  ========== OfTypeの結果 ==========
  Gsf.Samples.LinqSamples16+Person
  Gsf.Samples.LinqSamples16+Person
  Gsf.Samples.LinqSamples16+Customer
  Gsf.Samples.LinqSamples16+Customer
  Gsf.Samples.LinqSamples16+Person
  ========== OfTypeの結果 ==========
  Gsf.Samples.LinqSamples16+Customer
  Gsf.Samples.LinqSamples16+Customer
  ========== Genericではないコレクションを変換 ==========
  10
  20
  30
  40


追記:
書き忘れてました。
OfTypeメソッドは、他の変換演算子と違い、ソースシーケンスのスナップショットを作成しません。
つまり、

IEnumerable<Customer> c = persons.OfType<Customer>();

として取得した、シーケンスcを列挙(ループ)する度に評価されます。