【发布时间】:2011-12-23 20:51:43
【问题描述】:
动机:
在阅读 Mark Seemann 在Code Smell: Automatic Property 上的博客时,他在接近尾声时说:
底线是自动属性很少适用。 实际上,它们仅适用于属性类型为 值类型和所有可能的值都是允许的。
他以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 声明为编译时常量。我怎么能模仿呢?如果不调用构造函数或公开Celsius 存储int 的实现细节,我看不到创建Celsius 对象的方法。
- 我是否缺少任何可用性功能?
- 是否有更好的模式来创建自定义单字段值类型?
【问题讨论】:
-
查看这个问题(有人回答你“缺少可用性功能”部分)-stackoverflow.com/questions/441309/why-are-mutable-structs-evil 并从中链接。适用于所有值类型。
-
+1 回答关于变得更加 SOLID 的问题。
-
@Alexei - 我以前读过所有“可变结构是邪恶的”的帖子。我同意。问题是,如果我将私有字段设为只读,则 Celcius.MaxValue 会调用需要Celsius.MaxValue 已定义的构造函数。这是循环的,会导致运行时异常。这就是我在 MaxValue 定义中使用默认构造函数的原因。你知道解决这个问题的方法吗?一个特殊用途的“不检查边界”的私有构造函数感觉不对。
-
我没有意识到这一点。我认为拥有为给定类型创建常量的特殊方法(私有 CreateConstantValue()?)对于自我记录代码很有用 - 现在查看代码无法知道为什么必须调用默认构造函数。
标签: c# design-patterns encapsulation value-type solid-principles