設定ファイルの読み込みでクラスのインスタンス化にカスタムのTypeConverterを使う

TypeConverterAttributeにConfigurationConverterBaseを継承したクラスを指定すると任意のクラスをインスタンス化できます。
TypeConverterを使わなくても、プロパティをstringで取得してからインスタンス化すれば同じようなことは実現できますが、TypeConverterを使うとメリットがあります。

  • 例外をConfigurationErrorsExceptionに変換してくれる(ConfigurationErrorsExceptionは、設定ファイルの何行目に問題があったかを示してくれます)
  • 変換結果をスレッドセーフにキャッシュしてくれる

下記のHogeSectionクラスに記述したように、「type」は設定ファイルからのインタフェース、「Instance」はプログラムからのインタフェースといったように区別して記述できるのがgoodだと思います。

ConfigurationSectionやTypeConverterのサブクラス


public class HogeSection : ConfigurationSection
{
[ConfigurationProperty("type", IsRequired = true)]
[TypeConverter(typeof(HogeConvertor))]
public IHoge Instance
{
get { return (IHoge) base["type"]; }
}
}

public class HogeConvertor : ConfigurationConverterBase
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
var type = Type.GetType((string) value);
if (type == null)
{
throw new ArgumentException(string.Format("value[{0}]が存在しない。", value));
}
if (!typeof(IHoge).IsAssignableFrom(type))
{
throw new ArgumentException(string.Format("value[{0}]がIHogeのサブタイプでない。", value));
}
return Activator.CreateInstance(type);
}

public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
throw new NotSupportedException();
}
}

public interface IHoge
{
string Message
{
get;
}
}

public class Hoge : IHoge
{
public string Message
{
get { return "Hello Hoge"; }
}
}

public class Foo : IHoge
{
public string Message
{
get { return "Hello Foo"; }
}
}

public class Bar
{
}

App.config


<?xml version="1.0" encoding="utf-8"?>

<configuration>
<configSections>
<section name="hoge1" type="ConfigTest.HogeSection, ConfigTest" />
<section name="hoge2" type="ConfigTest.HogeSection, ConfigTest" />
<section name="hoge3" type="ConfigTest.HogeSection, ConfigTest" />
<section name="hoge4" type="ConfigTest.HogeSection, ConfigTest" />
</configSections>
<hoge1 type="ConfigTest.Hoge, ConfigTest" />
<hoge2 type="ConfigTest.Foo, ConfigTest" />
<hoge3 type="ConfigTest.Bar, ConfigTest" />
<hoge4 type="ConfigTest.NotFound, ConfigTest" />
</configuration>

テスト


[TestMethod]
public void TestHoge()
{
var section = (HogeSection)ConfigurationManager.GetSection("hoge1");
var hoge = section.Instance;
Assert.AreEqual("Hello Hoge", hoge.Message);
}

[TestMethod]
public void TestFoo()
{
var section = (HogeSection)ConfigurationManager.GetSection("hoge2");
var hoge = section.Instance;
Assert.AreEqual("Hello Foo", hoge.Message);
}

[TestMethod]
public void TestNotAssignable()
{
try
{
ConfigurationManager.GetSection("hoge3");
Assert.Fail();
} catch (ConfigurationErrorsException ex)
{
// The value of the property 'type' cannot be parsed. The error is: value[ConfigTest.Bar, ConfigTest]がIHogeのサブタイプでない。 (c:\users\nakamura\documents\visual studio 2010\Projects\ConfigTest\ConfigTest\bin\Debug\ConfigTest.dll.config line 12)
Console.WriteLine(ex.Message);
}
}

[TestMethod]
public void TestNotFound()
{
try
{
ConfigurationManager.GetSection("hoge4");
Assert.Fail();
}
catch (ConfigurationErrorsException ex)
{
// The value of the property 'type' cannot be parsed. The error is: value[ConfigTest.NotFound, ConfigTest]が存在しない。 (c:\users\nakamura\documents\visual studio 2010\Projects\ConfigTest\ConfigTest\bin\Debug\ConfigTest.dll.config line 13)
Console.WriteLine(ex.Message);
}
}

[TestMethod]
public void TestCache()
{
var section1 = (HogeSection)ConfigurationManager.GetSection("hoge1");
var section2 = (HogeSection)ConfigurationManager.GetSection("hoge1");
Assert.AreSame(section1.Instance, section2.Instance);
}


.NET4.0のC#で試しました。