string型をint型もしくはenum型に動的に変換する

## はじめに

string型の変数をint型もしくはenum型に変換することを考えます。変換の直前まで変換後の型は分からず動的に変換することを想定しています。

```csharp
// 結果確認用プログラム
class Program
{
    // 変換後のenum
    enum Status
    {
        Success = 0,
        Error = 99,
    }

    static void Main(string[] args)
    {
        {
            var convertedText = ConvertToTextType<int>("99");
            Console.WriteLine(convertedText.GetType().Name + ", " + convertedText);
            // Int32, 99
        }

        {
            var convertedText = ConvertToTextType<Status>("99");
            Console.WriteLine(convertedText.GetType().Name + ", " + convertedText);
            // Status, Error
        }
    }

    // text を 型 T に変換するメソッド
    private static object ConvertToTextType<T>(string text)
    {
        // ここに何か実装したい
    }
}
```

結果確認用として、文字列`99`をint型およびenum型に変換できているか確認するプログラムを用意しました。型変換の具体的な実装はメソッド`ConvertToTextType`で行うものとしパラメータTで変換後の型を指定します。

## 自力で実装した場合

はじめに自力で実装するとどのような感じになるのか確認します。

```csharp
    // text を 型 T に変換するメソッド
    private static object ConvertToTextType<T>(string text) where T : struct
    {
        if (typeof(T) == typeof(int))
        {
            var canConvert = int.TryParse(text, out int result);
            if (!canConvert)
            {
                throw new Exception("エラー");
            }
            return result;
        }
        else if (typeof(T).IsEnum)
        {
            var canConvert = Enum.TryParse(text, out T result);
            if (!canConvert)
            {
                throw new Exception("エラー");
            }
            return result;
        }
        return default(T);
    }
```

適当に実装した例がこちらです。変換後の型を自力で調べ、エラー処理をし、変換したものを返すという構造になっています。これには以下のような課題があります。

* エラー処理を自前で実装しなければいけない。
* 変換後の型が増えるたびにif文が増えていき冗長となる。例えばdouble型への変換も追加したいとなった場合。

以降このメソッドに改良を加えたいと思います。

## TypeConverterを利用

```csharp
    // text を 型 T に変換するメソッド
    private static object ConvertToTextType<T>(string text)
    {
        if (typeof(T) == typeof(int))
        {
            var converter = new Int32Converter();
            return converter.ConvertFrom(text);
        }
        else if (typeof(T).IsEnum)
        {
            var converter = new EnumConverter(typeof(T));
            return converter.ConvertFrom(text);
        }
        return default(T);
    }
```

.NetFrameWorkのTypeConverterクラスのサブクラスである`Int32Converter`、`EnumConverter`を用いて先程のプログラムを改良したものがこちらです。既存のクラスを利用したことでエラー処理を自前で実装する必要がなくなりました。

しかし先程にもあった、変換後の型が増えるたびにif文が増えていくという課題が残っています。

## TypeDescriptor.GetConverterを利用

```csharp
    // text を 型 T に変換するメソッド
    private static object ConvertToTextType<T>(string text)
    {
        var converter = TypeDescriptor.GetConverter(typeof(T));
        return converter.ConvertFrom(text);
    }
```

上記を更に改良し、型を判別するためのif文を書かなくてすむように改良したのがこちらです。.NetFrameWorkの`TypeDescriptor.GetConverter`メソッド利用して、先程は自力で行っていた`TypeConverter`の生成を肩代わりさせています。Tがintならconverterに`Int32Converter`が、enumなら`EnumConverter`が設定されます。

これによって自力で実装する場合に比べてシンプルに型変換のプログラムを記述できることが確認できました。

## TypeDescriptor.GetConverterで自作クラスを扱いたい場合

TypeDescriptor.GetConverterメソッドで引数に自作クラスを設定して動作させてもうまく動かないと思います。動作させたい場合は変換後のクラスに対して`TypeConverterAttribute`を設定する必要があります。

```csharp
class Program
{
    static void Main(string[] args)
    {
        // string型からMyClass型への変換
        var converter = TypeDescriptor.GetConverter(typeof(MyClass));
        var convertedItem = converter.ConvertFrom("aaa");
    }
}

[TypeConverter(typeof(MyConverter))]
public class MyClass
{
    public string X { get; set; }
}

// 自作Converter
public class MyConverter : TypeConverter
{
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string)
        {
            return new MyClass { X = (string)value };
        }
        return base.ConvertFrom(context, culture, value);
    }
}
```

ここでは変換後の自作クラス`MyClass`に対して、自作した`MyConverter`を`TypeConverterAttribute`に渡すことで`TypeDescriptor.GetConverter`でも扱えるようになっています。これによってstring型をMyClass型に変換できるようになりました。

`MyConverter`の実装は簡略化しています。詳しくは下記リンクが参考になると思います。

参考: [型コンバーターを実装する](https://msdn.microsoft.com/ja-jp/library/ayybcxe5(v=vs.120).aspx)


##  まとめ

* `TypeDescriptor.GetConverter`を使うと動的な型変換を楽に実装できる。
* 変換後のクラスが自作クラスであっても`TypeConverterAttribute`を利用することで`TypeDescriptor.GetConverter`で汎用的に扱える。