tekitoumemo’s diary

思ったことを書くだけ。長文版Twitter

【C#、ASP.NET MVC】独自クラスのプロパティをNULL以外の値で初期化する


技術ネタというよりはテクニックネタ。

独自クラスのプロパティを初期化をするときにStringやらNullableやらリストやらをdefault(T)で初期化するとNULLになってしまいます。

default(T)とは


単純に初期化をするだけなのですが、ジェネリックで指定出来るので0やらNULLやらと型によって値を変えなくて良いやつです。

規定値がNULLだとビミョーなのか

規定値がNULLなのでその通りに扱えば良いのですが、現場では意外に規定値以外の初期化は必要になったりします。例えば、entityframeworkやDapper※はテーブルと同じ形のモデルクラスを持っていて、とあるテーブルのカラムがNULL許容していたらNullable<T>となってしまいます。実際にテーブルのモデルをビューモデルで扱ったりすると、0と表示させたい場合もあるのでNULLは扱いにくかったりします。
※Dapperは独自でテーブルのモデルクラスを作るので、全く同じとは限りません。entityframeworkもモデルクラスをカスタム出来るので、全く同じとは限りませんが、自動で作るという点で考えると全く同じという認識でも問題ないかもしれません。

今回はクラスのプロパティを一括してNULL以外の規定値に変えるプログラムを作ったので、解説します。今回のプログラムはNULLは0やnewなどの初期化をし、すでに初期化されているプロパティは変換しません。

なぜこれを作ったのかというと、それぞれのビジネスロジックやモデル内で以下のような初期化コードを見つけたりします。

model.hoge1 = 0;
model.hoge2 = ””;

べつにこれでもいいっちゃいいのですが、無法地帯になると肥大化しまくります。ここら辺の問題はモデルに紐づくのでモデルのベースクラスに定義します。

     /// <summary>
    /// Baseクラス
    /// </summary>
    public class BaseClass
    {
        /// <summary>
        /// 特定の規定値で初期化を行う
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public void InitializingAbstractionClass<T>()
        {
            var properties = typeof(T).GetProperties();
            foreach (var property in properties)
            {
                var value = typeof(T).GetProperty(property.Name).GetValue(this, null);

                // 値がない場合に初期化
                if (value == null)
                {
                    var returnType = property.GetMethod.ReturnType;

                    // string型は既定値を空白にする
                    if (returnType == typeof(string))
                        value = string.Empty;
                    // Nullable型は既定値を0にする
                    else if (returnType == typeof(Nullable<byte>))
                        value = (byte)0;
                        ・・・
                    // その他、自前クラス等を初期化する
                    else
                    {
                        var currentType = Type.GetType(typeof(T).GetProperty(property.Name).PropertyType.FullName);
                        value = Activator.CreateInstance(currentType, null);
                    }
                }
                property.SetValue(this, value);
            }
        }

まず、ジェネリックで渡されたTypeからプロパティ一覧を取得します。

typeof(T).GetProperties();

プロパティのGetMethod.ReturnTypeからプロパティのTypeを取得し、Typeの初期化を行います。

if (returnType == typeof(string))
    value = string.Empty;
// Nullable型は既定値を0にする
else if (returnType == typeof(Nullable<byte>))
    value = (byte)0;

独自クラスなどnewしなければいけないプロパティについては、インスタンスを作成します。

Activator.CreateInstance(currentType, null);

最後にSetValueするだけです。

SetValue(this, value);

このクラスをビューモデルなどに継承させ、初期化の度に呼び出したり、コンストラクタに利用したりしてもよいと思います。以下のような形で呼び出します。

model.InitializingAbstractionClass<Hoge>();

以下のクラスがすべて初期化されました。

/// <summary>
/// Hogeクラス
/// </summary>
public class Hoge : BaseClass
{
    public Nullable<byte> Hoge1 { get; set; }
    public Nullable<short> Hoge2 { get; set; }
    public Nullable<ushort> Hoge3 { get; set; }
    public Nullable<int> Hoge4 { get; set; }
    public Nullable<uint> Hoge5 { get; set; }
    public Nullable<long> Hoge6 { get; set; }
    public Nullable<ulong> Hoge7 { get; set; }
    public Nullable<float> Hoge8 { get; set; }
    public Nullable<double> Hoge9 { get; set; }
    public Nullable<decimal> Hoge10 { get; set; }
    public string Hoge11 { get; set; }
    public ChildHoge Hoge12 { get; set; }
}

// 結果
Hoge1 = 0
Hoge2 = 0
Hoge3 = 0
Hoge4 = 0
Hoge5 = 0
Hoge6 = 0
Hoge7 = 0
Hoge8 = 0
Hoge9 = 0
Hoge10 = 0
Hoge11 = ""
Hoge12 = ChildHoge()

これで無駄な初期化処理がなくなりコードがすっきりするので、ジェネリックおすすめです。正直、ざっと作ったので実用的かといわれると微妙ですが、参考程度にこんなものもあるんだぐらいの認識でよいかと思います。

PS:動的Typeをキャストする方法ってないの?
Convert.ChangeType メソッド (Object, Type) (System)
これだとキャストできなかったりするので、簡単な方法でやり方を知っている人がいたら教えてください。