动机:
在 Code Smell: Automatic Property 上阅读 Mark Seemann 的博客时他在接近尾声时说:
The bottom line is that automatic properties are rarely appropriate. In fact, they are only appropriate when the type of the property is a value type and all conceivable values are allowed.
他以 int Temperature
为例说明难闻的气味,并建议最好的解决方法是单位特定值类型,如摄氏度。所以我决定尝试编写一个自定义 Celsius 值类型,它封装了所有边界检查和类型转换逻辑,作为练习更多 SOLID .
基本要求:
- 不可能有无效值
- 封装转换操作
- 有效应对(等同于 int its replacing)
- 尽可能直观地使用(尝试 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 常量而不是只读?看看 BCL,我喜欢 int 的元数据定义方式。清楚地将 MaxValue 和 MinValue 声明为编译时常量。我怎样才能模仿呢?我看不出有什么方法可以在不调用构造函数或不公开 Celsius 存储 int 的实现细节的情况下创建 Celsius 对象。
- 我是否缺少任何可用性功能?
- 是否有更好的模式来创建自定义单字段值类型?
最佳答案
Is there a way to make MinValue/MaxValue const instead of readonly?
没有。但是,BCL 也不这样做。例如,DateTime.MinValue是静态只读
。您当前的方法对于 MinValue
和 MaxValue
是合适的。
至于您的另外两个问题 - 可用性和模式本身。
就个人而言,我会避免像这样的“温度”类型的自动转换(隐式转换运算符)。温度不是整数值(事实上,如果您要这样做,我认为它应该是 float - 93.2 摄氏度是完全有效的。)将温度视为整数,尤其是将任何整数值隐含地视为温度似乎是不合适的,并且是错误的潜在原因。
我发现具有隐式转换的结构通常会导致比它们解决的更多的可用性问题。强制用户写:
Celsius c = new Celcius(41);
并不比从整数隐式转换困难多少。然而,它要清楚得多。
关于c# - 创建简单高效值类型的模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8040999/