いろいろ備忘録日記

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

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


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


Castメソッドは、OfTypeメソッドとほとんど一緒です。
違いは、OfTypeメソッドでは変換対象となる型ではないオブジェクトは
除外されますが、Castメソッドの場合InvalidCastExceptionが発生します。

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

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

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


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

persons.Cast<Customer>();

とすると、OfTypeメソッドではCustomerのみのシーケンスとなりますが
Castメソッドの場合は例外が発生します。


尚、OfTypeメソッドと同じくCastメソッドもソースシーケンスのスナップショットを作成しません。
Castメソッドの結果は、通常のLinqクエリと同じく列挙(ループ)の際に評価されます。


なので、

IEnumerable<Person> p = persons.Cast<Person>();

とした後で、元のシーケンスに対して

persons.Add(new Person());
foreach (var tmp in p)
{
    Console.WriteLine(tmp);
}

とすると、後で追加したオブジェクトも列挙されます。


以下、サンプルです。

    #region LinqSamples-17
    public class LinqSamples17 : 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" }
            };

            //
            // Castメソッドを利用することにより、特定の型のみのシーケンスに変換することができる。
            // OfTypeメソッドと違い、Castメソッドは単純にキャスト処理を行う為、キャスト出来ない型が
            // 含まれている場合は例外が発生する。
            // (OfTypeメソッドの場合、除外される。)
            //
            //
            // 尚、Castメソッドは他の変換演算子とは違い、ソースシーケンスのスナップショットを作成しない。
            // つまり、通常のクエリと同じく、Castで取得したシーケンスが列挙される度に評価される。
            // 変換演算子の中で、このような動作を行うのはAsEnumerableとOfTypeとCastである。
            //
            Console.WriteLine("========== Cast<Person>の結果 ==========");
            foreach (var data in persons.Cast<Person>())
            {
                Console.WriteLine(data);
            }
            
            //////////////////////////////////////////////////////////
            //
            // 以下のpersons.Cast<Customer>()はPersonオブジェクトをCustomerオブジェクトに
            // キャスト出来ない為、例外が発生する。
            //
            Console.WriteLine("========== Cast<Customer>の結果 ==========");
            try
            {
                foreach (var data in persons.Cast<Customer>())
                {
                    Console.WriteLine(data);
                }
            } 
            catch(InvalidCastException ex)
            {
                Console.WriteLine(ex.Message);
            }
            
            //
            // 元々GenericではないリストをIEnumerable<T>に変換する場合にも利用出来る.
            // 当然、Castメソッドを利用する場合は、コレクション内部のデータが全てキャスト可能で
            // ないといけない。
            //
            ArrayList arrayList = new ArrayList();
            arrayList.Add(10);
            arrayList.Add(20);
            arrayList.Add(30);
            arrayList.Add(40);
            
            Console.WriteLine("========== Genericではないコレクションを変換 ==========");
            IEnumerable<int> intList = arrayList.Cast<int>();
            foreach (var data in intList)
            {
                Console.WriteLine(data);
            }
        }
    }
    #endregion


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

  ========== Castの結果 ==========
  Gsf.Samples.LinqSamples17+Person
  Gsf.Samples.LinqSamples17+Person
  Gsf.Samples.LinqSamples17+Customer
  Gsf.Samples.LinqSamples17+Customer
  Gsf.Samples.LinqSamples17+Person
  ========== Castの結果 ==========
  型 'Person' のオブジェクトを型 'Customer' にキャストできません。
  ========== Genericではないコレクションを変換 ==========
  10
  20
  30
  40