创建简单有效的值类型的模式 [英] Pattern for Creating a Simple and Efficient Value type
问题描述
动机:
在阅读Mark Seemann的博客代码气味:自动属性他接近尾声说:
底线是自动属性很少适合。
事实上,它们只适用于属性类型为
值类型,并且允许所有可设想的值。
他给出了 int Temperature
作为难闻气味的一个例子,并建议最好的解决方案是单位特定值类型,如摄氏度。所以我决定尝试编写一个自定义的Celsius值类型,它将所有边界检查和类型转换逻辑作为一个练习,更多的是 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}; //绝对零
public static readonly Celsius MaxValue = new Celsius(){m_value = int.MaxValue};
private Celsius(int temp)
{
if(temp< Celsius.MinValue)
throw new ArgumentOutOfRangeException(temp,Value不能少于摄氏温度(绝对零));
if(temp> Celsius.MaxValue)
throw new ArgumentOutOfRangeException(temp,Value can not 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;
}
//其他数字类型的运算符
public override string ToString()
{
return m_value.ToString ();
}
//覆盖等于,HashCode等
}
测试:
[TestClass]
public class TestCelsius
{
[TestMethod]
public void QuickTest()
{
Celsius c = 41;
摄氏c2 = c;
int temp = c2;
Assert.AreEqual(41,temp);
Assert.AreEqual(41,c.ToString());
}
[TestMethod]
public void OutOfRangeTest()
{
try
{
Celsius c = -300;
Assert.Fail(不能分配-300);
}
catch(ArgumentOutOfRangeException)
{
// pass
}
catch(异常)
{
Assert.Fail (抛出异常);
}
}
}
问题: / strong>
- 有没有办法使MinValue / MaxValue const而不是只读?查看BCL我喜欢 int 的元数据定义清楚地说明了MaxValue和MinValue作为编译时常数。我怎么模仿呢?我没有看到创建一个Celsius对象,而不用调用构造函数或者暴露Celsius存储int的实现细节。
- 我是否缺少任何可用性功能? / strong>
- 创建自定义单个字段值类型是否有更好的模式?
有没有办法使MinValue / MaxValue const而不是只读?
否。但是,BCL也不这样做。例如, DateTime.MinValue 是 static readonly
。您当前的方法,适用于 MinValue
和 MaxValue
。
<对于您的其他两个问题 - 可用性和模式本身。
个人而言,我会避免使用温度类型的自动转换(隐式转换运算符),如这个。温度不是一个整数值(实际上,如果你要这样做,我会认为它应该是浮点数 - 93.2摄氏度是完全有效的。)将温度作为整数处理,特别是处理任何整数值,因为温度看起来是不适当的,并且是错误的潜在原因。
我发现使用隐式转换的结构往往会导致更多的可用性问题。强制用户写:
Celsius c = new Celcius(41);
真的比从整数隐式转换要困难得多。然而,这是非常明确的。
Motivation:
In reading Mark Seemann’s blog on Code Smell: Automatic Property he says near the end:
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.
He gives int Temperature
as an example of a bad smell and suggests the best fix is unit specific value type like Celsius. So I decided to try writing a custom Celsius value type that encapsulates all the bounds checking and type conversion logic as an exercise in being more SOLID.
Basic requirements:
- Impossible to have an invalid value
- Encapsulates conversion operations
- Effient coping (equivalent to the int its replacing)
- As intuitive to use as possible (trying for the semantics of an int)
Implementation:
[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...
}
Tests:
[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");
}
}
}
Questions:
- Is there a way to make MinValue/MaxValue const instead of readonly? Looking at the BCL I like how the meta data definition of int clearly states MaxValue and MinValue as compile time constants. How can I mimic that? I don’t see a way to create a Celsius object without either calling the constructor or exposing the implementation detail that Celsius stores an int.
- Am I missing any usability features?
- Is there a better pattern for creating a custom single field value type?
Is there a way to make MinValue/MaxValue const instead of readonly?
No. However, the BCL doesn't do this, either. For example, DateTime.MinValue is static readonly
. Your current approach, for MinValue
and MaxValue
is appropriate.
As for your other two questions - usability and the pattern itself.
Personally, I would avoid the automatic conversions (implicit conversion operators) for a "temperature" type like this. A temperature is not an integer value (in fact, if you were going to do this, I would argue that it should be floating point - 93.2 degrees C is perfectly valid.) Treating a temperature as an integer, and especially treating any integer value implicitly as a temperature seems inappropriate and a potential cause of bugs.
I find that structs with implicit conversion often cause more usability problems than they address. Forcing a user to write:
Celsius c = new Celcius(41);
Is not really much more difficult than implicitly converting from an integer. It is far more clear, however.
这篇关于创建简单有效的值类型的模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!