创建简单有效的值类型的模式 [英] Pattern for Creating a Simple and Efficient Value type

查看:153
本文介绍了创建简单有效的值类型的模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

动机:



在阅读Mark Seemann的博客代码气味:自动属性他接近尾声说:


底线是自动属性很少适合。
事实上,它们只适用于属性类型为
值类型,并且允许所有可设想的值。


他给出了 int Temperature 作为难闻气味的一个例子,并建议最好的解决方案是单位特定值类型,如摄氏度。所以我决定尝试编写一个自定义的Celsius值类型,它将所有边界检查和类型转换逻辑作为一个练习,更多的是 SOLID



基本要求


  1. 不可能有一个无效值

  2. 封装转换操作

  3. 有效应付(相当于int替换)

  4. 尽可能直观地使用(尝试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:

  1. Impossible to have an invalid value
  2. Encapsulates conversion operations
  3. Effient coping (equivalent to the int its replacing)
  4. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆