動機:
Code Smell: Automatic Propertyに関する Mark Seemann のブログを読んで、彼は最後の方で次のように述べています。
肝心なのは、自動プロパティが適切であることはめったにないということです。実際、プロパティの型が値型であり、考えられるすべての値が許可されている場合にのみ適切です。
彼はint Temperature
悪臭の例を挙げ、摂氏のような単位固有の値のタイプが最善の解決策であることを示唆しています。そこで、よりSOLIDになるための演習として、すべての境界チェックと型変換ロジックをカプセル化するカスタム摂氏値型を作成してみることにしました。
基本要件:
- 無効な値を持つことはできません
- 変換操作をカプセル化します
- 効率的な対処 (int を置き換えることと同等)
- 可能な限り直観的に使用する (int のセマンティクスを試す)
実装:
[System.Diagnostics.DebuggerDisplay("{m_value}")]
public struct Celsius // : IComparable, IFormattable, etc...
{
private int m_value;
public static readonly Celsius MinValue = new Celsius() { m_value = -273 }; // absolute zero
public static readonly Celsius MaxValue = new Celsius() { m_value = int.MaxValue };
private Celsius(int temp)
{
if (temp < Celsius.MinValue)
throw new ArgumentOutOfRangeException("temp", "Value cannot be less then Celsius.MinValue (absolute zero)");
if (temp > Celsius.MaxValue)
throw new ArgumentOutOfRangeException("temp", "Value cannot be more then Celsius.MaxValue");
m_value = temp;
}
public static implicit operator Celsius(int temp)
{
return new Celsius(temp);
}
public static implicit operator int(Celsius c)
{
return c.m_value;
}
// operators for other numeric types...
public override string ToString()
{
return m_value.ToString();
}
// override Equals, HashCode, etc...
}
テスト:
[TestClass]
public class TestCelsius
{
[TestMethod]
public void QuickTest()
{
Celsius c = 41;
Celsius c2 = c;
int temp = c2;
Assert.AreEqual(41, temp);
Assert.AreEqual("41", c.ToString());
}
[TestMethod]
public void OutOfRangeTest()
{
try
{
Celsius c = -300;
Assert.Fail("Should not be able to assign -300");
}
catch (ArgumentOutOfRangeException)
{
// pass
}
catch (Exception)
{
Assert.Fail("Threw wrong exception");
}
}
}
質問:
- 読み取り専用の代わりに MinValue/MaxValue を const にする方法はありますか? BCL を見ると、 intのメタデータ定義でMaxValue と MinValue がコンパイル時の定数として明確に示されているのが気に入っています。どうすればそれを模倣できますか?コンストラクターを呼び出すか、摂氏が int に格納する実装の詳細を公開せずに摂氏オブジェクトを作成する方法がわかりません。
- ユーザビリティ機能が不足していませんか?
- カスタム単一フィールド値タイプを作成するためのより良いパターンはありますか?