徳島ゲーム開発ごっこ 技術ブログ

ゲームを作るために役に立ったり立たなかったりする技術を学んでいきます!

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

 C#のLINQの関数であるCast()の使い方についてです。
 配列やリストなどの要素を指定した型に変換することが出来ます。
f:id:urahimono:20180702000828p:plain


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

この型に変換するよ

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

 そのリストの要素を纏めてキャストしたいときはLINQCast()があります。

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

 以下の例ではこのような構成のクラスに対してCast()を使っています。

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 )
    {
        ClassB[] parametersA    = new ClassB[]
        {
            new ClassB() { Name = "正一郎" },
            new ClassB() { Name = "清次郎" },
            new ClassB() { Name = "誠三郎" },
            new ClassC() { Name = "征史郎" },
        };

        BaseClass[] parametersB  = new BaseClass[]
        {
            new ClassB() { Name = "正一郎" },
            new ClassB() { Name = "清次郎" },
            new ClassB() { Name = "誠三郎" },
            new ClassC() { Name = "征史郎" },
        };

        // アップキャスト
        IEnumerable<BaseClass>  reaultUp    = parametersA.Cast<BaseClass>();
        // ダウンキャスト
        IEnumerable<ClassB>     reaultDown  = parametersB.Cast<ClassB>();

        // 結果発表
        System.Console.WriteLine( "parameters   :{0}", parametersA.Text() );
        System.Console.WriteLine( "アップキャスト:{0}", reaultUp.Text() );
        System.Console.WriteLine( "ダウンキャスト:{0}", reaultDown.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

parameters :[Type:B, Name:正一郎], [Type:B, Name:清次郎], [Type:B, Name:誠三郎
], [Type:C, Name:征史郎],
アップキャスト:True
ダウンキャスト:True

変換出来ない場合はどうなるの

 さて、ではCast()をつかった際に、もしキャストが出来ないものがあった場合はどうなるのでしょうか。

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 ClassB() { Name = "正一郎" },
            new ClassB() { Name = "清次郎" },
            new ClassB() { Name = "誠三郎" },
            new ClassC() { Name = "征史郎" },
        };

        bool reault = false;

        try
        {
            // ClassC以外のクラスもあるから、成功しそうにないけど……。
            reault  = parameters.Cast<ClassC>().Any();
        }
        catch( System.Exception i_exception )
        {
            System.Console.WriteLine( "{0}", i_exception );
            System.Console.ReadKey();
            return;
        }

        // 結果発表
        System.Console.WriteLine( "parameters   :{0}", parameters.Text() );
        System.Console.WriteLine( "cast         :{0}", reault );

        // 入力待ち用
        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

System.InvalidCastException: 型 'ClassB' のオブジェクトを型 'ClassC' にキャストできません。

 その場合は、例外が発生してしまいます。

OfTypeとCastはなにが違うの

 LINQにはCast()に似た関数としてOfType()があります。
www.urablog.xyz

 この関数の違いはなんなのかと。
 てっきり、例外が発生するか否かの違いだと思っていました。
 調べたところ、このような記事を見かけました。

outside6.wp.xdomain.jp

 なるほど、isasの違いから要素がnullだった場合に挙動が変わってくるわけですね。
 試してみましょう。

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 ClassB() { Name = "正一郎" },
            null,
            new ClassB() { Name = "誠三郎" },
            new ClassC() { Name = "征史郎" },
        };

        int countCast   = parameters.Cast<ClassB>().Count();
        int countOfType = parameters.OfType<ClassB>().Count();

        // 結果発表
        System.Console.WriteLine( "parameters  :{0}", parameters.Text() );
        System.Console.WriteLine( "countCast   :{0}", countCast );
        System.Console.WriteLine( "countOfType :{0}", countOfType );

        // 入力待ち用
        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

parameters :[Type:B, Name:正一郎], [], [Type:B, Name:誠三郎], [Type:C, Name:征
史郎],
countCast :4
countOfType :3

 なるほど、確かに結果が違いますね。
 nullの場合、Cast()なら成功扱いになりますが、OfType()の場合は失敗扱いになります。

 この辺りに気を付けて、Cast()を七兆回ほど使ってみてください。

LINQのリンク