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

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

【C#】そうか、enum型をジェネリックで制約するときはstructを使えばよかったのか

 先日、デジゲー博に行った際に購入した「Unityマニアックス」。
bootlegstore.stores.jp

 これは、とても勉強になる。

 なかでも特に気になったのが、この部分だ。

using UnityEngine;

public class TestComponent : MonoBehaviour
{
    private void Start()
    {
        PrintEnumList<EPet>();
        PrintEnumList<ELanguage>();
    }

    private void PrintEnumList<T>() where T : struct
    {
        System.Type type = typeof( T );
        T[] values = (T[])System.Enum.GetValues( type );

        Debug.LogFormat( "{0} enum list.", type.Name );
        for( int i = 0; i < values.Length; ++i )
        {
            Debug.Log( values[ i ] );
        }
    }

    private enum EPet
    {
        Dog,
        Cat,
        Hamster,
    }

    private enum ELanguage
    {
        Japanese,
        English,
    }

} // class TestComponent

 そう、ジェネリックをenum型として制約させたいときはstructを使えばいいということだ。

 いろいろためになることが書いてある中、それを選ぶ?
 と言われそうですが、この方法は探してたんですよ。

 このジェネリック関数、またはクラスに指定したいのはenumだ。
 ただ、whereを使ってどう制約していいかがわからない。
 enum型を直接whereで指定することはできないし、かといって制約しなかったら何でもかんでも型を指定されてしまう……。

 そんな中この本に書いてあった、structでの制約方法だ。

 そうか、enumが値型だ。
 それならばstructで制約することが出来る。
 この方法は思い浮かばなかった。

 コードの戦略性がまた一つ広がった気がする。

 ……でも値型による制約ってことは、こんなこともできちゃうよね。

private void Start()
{
    PrintEnumList<EPet>();
    PrintEnumList<ELanguage>();

    PrintEnumList<int>();
    PrintEnumList<float>();
    PrintEnumList<Sample>();
}

private struct Sample
{
    public int      number;
    public float    single;
}

 うん、ビルド通っちゃったよね。
 intとかfloatとか、自分で作った構造体とかも指定できちゃうようね。
 これ実行したらどうなるんだろう。
 System.Enum.GetValues()あたりでヤバイことになりそうだけど。

f:id:urahimono:20171127211814p:plain

ArgumentException: enumType is not an Enum type.

 トラップカードオープン、例外発動!

 だよね、やっぱりそうなるよね。
 うーん、structを使うことで制約性は跳ね上がったけど、完全ではないということか……。

 どうしようかね。

解決案 その一
処理の中身を見れば、指定すべき型はenumでなければならないことは明白なはず。
にもかかわらず、enum以外の型を指定する糞プログラマーには鉄拳制裁を与える。

 うん、それはちょっと横暴かな。
 今回の例はわかりやすかったけど、ジェネリッククラスだったりしたら、問題となる処理がすぐそばにあるとは限らないからね。

解決案 その二
コメントで"enum型を指定してね♥"と記述しておく。
にもかかわらず、enum以外の型を指定する糞プログラマーには鉄拳制裁を与える。

 うん、とりあえず暴力行為はやめようよ。
 まあ、それでもいいような気がする。
 でも折角なので、例外処理でも書いておこうよ。

private void PrintEnumList<T>() where T : struct
{
    System.Type type = typeof( T );

    T[] values = null;
    try
    {
        values = (T[])System.Enum.GetValues( type );
    }
    catch( System.Exception i_exception )
    {
        Debug.LogError( i_exception );
        return;
    }

    Debug.LogFormat( "{0} enum list.", type.Name );
    for( int i = 0; i < values.Length; ++i )
    {
        Debug.Log( values[ i ] );
    }
}

 えっ、例外処理とかダルイって。
 うーん、それじゃあせめてアサーションだけでも。
 確かSystem.Typeにはenumかどうか判断するプロパティがあった気がするから。

private void PrintEnumList<T>() where T : struct
{
    System.Type type = typeof( T );
    Debug.AssertFormat( type.IsEnum, "is not an Enum type" );

    T[] values = (T[])System.Enum.GetValues( type );

    Debug.LogFormat( "{0} enum list.", type.Name );
    for( int i = 0; i < values.Length; ++i )
    {
        Debug.Log( values[ i ] );
    }
}

 ふぅ、まあこんなところでしょうか。

 それにしても、やっぱり技術書を読むのは勉強になります。
 開発ばっかりもいいですが、たまには技術書もいろいろと読んでいかなくちゃいけませんよね。