うら干物書き

ゲームを作りたい!

【C#,LINQ】OfType~配列やリストの中で指定した型に変換できる要素を取得したいとき~

 C#のLINQの関数であるOfType()の使い方についてです。
 配列やリストなどのシーケンス内にて、指定した型に変換できる要素を取得することが出来ます。
f:id:urahimono:20180625231437p:plain


この記事には.NET Framework 4.6.1を使用しています。

この型に変換できる要素はあるのかな

 とあるクラスを継承したクラスがたくさんあり、そのデータの管理に一番ベースとなっているクラスのリストをデータとして持つ。
 そんなときありますよね。

 でもその中で特定のクラスのデータのみが欲しい場合はありませんか。
 そんなときはLINQOfType()が便利です。
 欲しい方をジェネリックで指定すれば、変換可能な要素のみを取得できます。

public static IEnumerable<TResult> OfType<TResult>( this IEnumerable source );
Enumerable.OfType(TResult) メソッド (IEnumerable) (System.Linq)

 早速以下の例で試してみましょう。
 以下の例ではクラス構成がこんな感じになっています。

f:id:urahimono:20180625231419p:plain

 ……クラス図あっているかな。
 このクラスに対して、OfType()を使ってみましょう。

Program.cs

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    private class BaseClass
    {
        public virtual string   Type    { get { return "-"; } }
        public string           Name    { get; set; }

        public override string ToString()
        {
            return string.Format( "Type:{0}, Name:{1}", Type, Name );
        }
    }
    private class ClassA : BaseClass
    {
        public override string  Type    { get { return "A"; } }
    }
    private class ClassB : BaseClass
    {
        public override string  Type    { get { return "B"; } }
    }
    private class ClassC : ClassB
    {
        public override string  Type    { get { return "C"; } }
    }

    static void Main( string[] args )
    {
        BaseClass[] parameters  = new BaseClass[]
        {
            new ClassA() { Name = "正一郎" },
            new ClassB() { Name = "清次郎" },
            new ClassB() { Name = "誠三郎" },
            new ClassC() { Name = "征史郎" },
        };

        // 各クラスに変換
        IEnumerable<BaseClass>  reaultBase  = parameters.OfType<BaseClass>();
        IEnumerable<ClassA>     reaultA     = parameters.OfType<ClassA>();
        IEnumerable<ClassB>     reaultB     = parameters.OfType<ClassB>();
        IEnumerable<ClassC>     reaultC     = parameters.OfType<ClassC>();

        // 結果発表
        System.Console.WriteLine( "parameters:{0}", parameters.Text() );
        System.Console.WriteLine( "BaseClassに変換できるもの:{0}", reaultBase.Text() );
        System.Console.WriteLine( "ClassAに変換できるもの   :{0}", reaultA.Text() );
        System.Console.WriteLine( "ClassBに変換できるもの   :{0}", reaultB.Text() );
        System.Console.WriteLine( "ClassCに変換できるもの   :{0}", reaultC.Text() );
        // 入力待ち用
        System.Console.ReadKey();
    }

    /// <summary>
    /// 簡易的なシーケンスのテキスト取得処理
    /// </summary>
    public static string Text<TSource>( this IEnumerable<TSource> i_source )
    {
        string text = string.Empty;
        foreach( var value in i_source )
        {
            text += string.Format( "[{0}], ", value );
        }
        return text;
    }

} // class Program

parameters:[Type:A, Name:正一郎], [Type:B, Name:清次郎], [Type:B, Name:誠三郎],
[Type:C, Name:征史郎],
BaseClassに変換できるもの:[Type:A, Name:正一郎], [Type:B, Name:清次郎], [Type:B,
Name:誠三郎], [Type:C, Name:征史郎],
ClassAに変換できるもの :[Type:A, Name:正一郎],
ClassBに変換できるもの :[Type:B, Name:清次郎], [Type:B, Name:誠三郎], [Type:C,
Name:征史郎],
ClassCに変換できるもの :[Type:C, Name:征史郎],

 こんな感じに使えます。

 他にもArrayListに対しても使えます。
 ArrayListにはいろんな型のものを渡せるので便利ではあるのですが、データが混沌としやすいです。
 ではOfType()を使って各型の要素を取得してみましょう。

Program.cs

using System.Linq;
using System.Collections;
using System.Collections.Generic;

public static class Program
{
    static void Main( string[] args )
    {
        // いろんな型が入ったデータ
        ArrayList list = new ArrayList() { 1, 3, "2", 5.5f };

        // 各クラスに変換
        IEnumerable<int>    reaultInt       = list.OfType<int>();
        IEnumerable<string> reaultString    = list.OfType<string>();
        IEnumerable<float>  reaultFloat     = list.OfType<float>();
        IEnumerable<long>   reaultLong      = list.OfType<long>();

        // 結果発表
        System.Console.WriteLine( "list:{0}", list.Text() );
        System.Console.WriteLine( "intに変換できるもの   :{0}", reaultInt.Text() );
        System.Console.WriteLine( "stringに変換できるもの:{0}", reaultString.Text() );
        System.Console.WriteLine( "floatに変換できるもの :{0}", reaultFloat.Text() );
        System.Console.WriteLine( "longに変換できるもの  :{0}", reaultLong.Text() );
        // 入力待ち用
        System.Console.ReadKey();
    }

    /// <summary>
    /// 簡易的なシーケンスのテキスト取得処理
    /// </summary>
    public static string Text( this IEnumerable i_source )
    {
        string text = string.Empty;
        foreach( var value in i_source )
        {
            text += string.Format( "[{0}], ", value );
        }
        return text;
    }

} // class Program

list:[1], [3], [2], [5.5],
intに変換できるもの :[1], [3],
stringに変換できるもの:[2],
floatに変換できるもの :[5.5],
longに変換できるもの :

 こんな感じで取得できます。
 ……まあコード上でこのようなデータは作らないとは思いますが、外部から情報を取得する場合には使うこともあると思います。

 こんな感じでOfType()を七兆回ほど使ってみてください。

LINQのリンク