amagoのエンジニアブログ

IT関連のネタを中心に記事を書いてます。

【C#】Genericsで型変換を使ってみた

こんにちはAmagoです。

 

C#のコードを書き始めて1年くらい経ちます。最近になって、Genericsの使い方について理解できたので、記事にしようかと思います。

私がプログラムを書くときに心がけていることがいくつかあります。

 
見やすく、保守し易いことです。

 

チーム開発ではこれに当てはまらないソースをよく見かけます。

同じような条件分岐が至る所に散りばめられており、仕様変更のたびに全部に修正を加えたりと・・・

それをそのままコピーペーストして無駄な処理が倍々とかね・・・

 

今回は自分が担当してた機能でジェネリクスを使ったらソースがかなりコンパクトになったので、こんな使い方もあるんだぞというご紹介です。

 

ジェネリクスとは

Generics(ジェネリクス)とは、処理内容は同じで型だけ違う場合に使います。(他にも色々あると思いますが、自分はそう認識してます)

ジェネリック - C# によるプログラミング入門 | ++C++; // 未確認飛行 C

ジェネリクスを使ってみよう

今回はint、long、Decimalのnull許容型の変数を別の数値型に変換するプログラムを作ってみよう思います。
今回作成したサンプルソースGitHubで公開してあります。よかったら見てください。
GitHub - hamaguchi-amago/GenericsSample: ジェネリクス サンプルソース ブログの記事で作りました。

intのnull許容型からintに変換してみる(ジェネリクスは使っていません。)

まずは、intのnull許容型からintに変換してみましょう、この場合はわざわざメソッドを作る必要はなく。Convert.ToInt32を使えばいいと思います。
[Sample1.cs]

using System;

namespace GenericsSample {
    class Sample1 {
        public void Main() {

            int? data = null;
            var result = Convert.ToInt32(data);

            Console.WriteLine("Type : " + result.GetType());
            Console.WriteLine("Value: " + result.ToString());
        }
    }
}

実行結果

Type : System.Int32
Value: 0

実行結果を見ると、nullを渡しても0が返ってきますね。

intとlongも変換してみる(こちらも、ジェネリクスを使っていません)

次は、intとlongを戻り値として受け取りたい場合です。
こちらも、専用のメソッドがあるのでわざわざ独自のメソッドを作る必要はなさそうですね。

[Sample2.cs]

using System;

namespace GenericsSample {
    class Sample2 {
        public void Main() {

            int? data1 = null;
            long? data2 = null;

            var result1 = Convert.ToInt32(data1);
            var result2 = Convert.ToInt64(data2);

            WriteData(result1);
            WriteData(result2);
        }

        private void WriteData(object data) {
            Console.WriteLine("Type : " + data.GetType());
            Console.WriteLine("Value: " + data.ToString());
            Console.WriteLine();
        }
    }
}

実行結果

Type : System.Int32
Value: 0

Type : System.Int64
Value: 0

結果も前回と同様に、両方ともnullを渡して0が返ってきますね。

戻り値も引数も指定したい場合は?

ここでとうとうジェネリクスの出番です。よくよく考えると今回の例はジェネリクスを使わなくてもConvertを使えば
良かったかもなぁと執筆中に気が付いてしまいましたが・・・

nullを受け取ったら1を返すとか100をかえす仕様だったり。
メソッド内でほかのメソッドを呼ぶ必要がある場合は無駄ではないよね?
と自分を慰めつつ続きを紹介していきます。

ジェネリックを使って作ったメソッドがこちらです。
[Sample3.cs]

        private T2 Convert<T1, T2>(T1 data) {
            if (data == null) {
                return (T2)(dynamic)(0);
            } else {
                return (T2)(dynamic)data;
            }
        }

メソッドの仕様は、T1型で受け取ったデータをT2型に変換して返し、nullの場合は0を返します。

ジェネリックの便利さがいまいちピンときていないかもしれないので、具体的に例を出します。
例えば、引数に『int?、long?、Decimal?』の3パターンの型のデータを受け取り、それぞれ『int、long、Decimal』の3パターンの型を返すメソッドを作る場合について考えてみます。
ジェネリックを使わずに型を指定すると3×3で9個、ここにもう一つ型が増えたら4×4で16個も同じようなメソッドを作らないといけませんよね。

そんなめんどくさいことをやらなくても型を指定して呼び出してあげれば、たった数ステップで仕様を満たすことが出来るのです。

次は呼び出し方についてみてみましょう。呼び出し方はこちらになります。
[Sample3.cs]

       private void Pattern1() {
            Int = null;
            Long = null;
            Decimal = null;

            var result_1_1 = Convert<int?, int>(Int);
            var result_1_2 = Convert<long?, long>(Long);
            var result_1_3 = Convert<Decimal?, Decimal>(Decimal);

            WriteData(result_1_1);
            WriteData(result_1_2);
            WriteData(result_1_3);
        }

        private void WriteData(object data) {
            Console.WriteLine("Type : " + data.GetType());
            Console.WriteLine("Value: " + data.ToString());
            Console.WriteLine();
        }

『T1』に該当する部分に渡したい型、『T2』に受け取りたい方を指定してあげればOKです。実行結果はこちらになります。

実行結果

Type : System.Int32
Value: 0

Type : System.Int64
Value: 0

Type : System.Decimal
Value: 0

3パターンともnullを受け取って0を返していますね。次は別の型に変換できているか見てみましょう。

[Sample3.cs]

        private void Pattern2() {
            Int = null;
            Long = null;
            Decimal = null;

            var result_2_1 = Convert<int?, Decimal>(Int);
            var result_2_2 = Convert<long?, int>(Long);
            var result_2_3 = Convert<Decimal?, int>(Decimal);

            WriteData(result_2_1);
            WriteData(result_2_2);
            WriteData(result_2_3);
        }

実行結果

Type : System.Decimal
Value: 0

Type : System.Int32
Value: 0

Type : System.Int32
Value: 0

result_2_1 はint?からDecimalに、result_2_2 はlong?からintに、result_2_3 はDecimal?からintに型が変換されて、nullを受け取ってるので0を返しています。
もちろん、null以外に数値が設定されている場合も正しく動きます。ちなみに変数Intに設定されている値の2,147,483,647はintの最大値です。
[Sample3.cs]

        private void Pattern3() {
            Int = 2147483647;
            Long = 2147483648;
            Decimal = 1000;

            var result_3_1 = Convert<int?, int>(Int);
            var result_3_2 = Convert<long?, long>(Long);
            var result_3_3 = Convert<Decimal?, Decimal>(Decimal);

            WriteData(result_3_1);
            WriteData(result_3_2);
            WriteData(result_3_3);
        }

実行結果

Type : System.Int32
Value: 2147483647

Type : System.Int64
Value: 2147483648

Type : System.Decimal
Value: 1000

  

最後に

最後まで読んでくださってありがとうございます。
ジェネリクスについて少しでも理解して、今度使ってみようと思った方がいれば幸いです。
今後も、知ってると少し楽ができるような開発ネタを紹介できればなと思います。

 

にほんブログ村 IT技術ブログへにほんブログ村

ブログランキング・にほんブログ村へにほんブログ村