强类型整数

时间:2010-08-10 11:46:55

标签: design-patterns strong-typing

作为一个爱好项目的思想实验,我一直在想办法确保不会发生这种微妙的错误/错字:

public void MyMethod(int useCaseId)
{
    // Do something with the useCaseId
}

public void SomeOtherMethod()
{
    int userId = 12;
    int useCaseId = 15;
    MyMethod(userId); // Ooops! Used the wrong value!
}

这个bug很难找到,因为没有编译时错误,你甚至不一定会在运行时获得异常。你只会得到“意想不到的结果”。

为了以简单的方式解决这个问题,我尝试使用空的枚举定义。有效地使用户id成为一种数据类型(不会像类或结构那么远):

public enum UseCaseId { // Empty… }

public enum UserId { // Empty… }

public void MyMethod(UseCaseId useCaseId)
{
   // Do something with the useCaseId
}

public void SomeOtherMethod()
{
   UserId userId = (UserId)12;
   UseCaseId useCaseId = (UseCaseId)15;
   MyMethod(userId); // Compile error!!
}
你觉得怎么样?

6 个答案:

答案 0 :(得分:6)

如果您遇到创建新类型以保留UserIdUseCaseId的麻烦,您几乎可以轻松地将它们变为简单类并使用来自{{1}的隐式转换运算符给你想要的语法:

int

这样,您就可以获得类型安全,而无需使用强制转换来丢弃您的代码。

答案 1 :(得分:2)

如果是Haskell并且我想这样做,我可能会这样做:

data UserId    = UserId    Int
data UseCaseId = UseCaseId Int

这样,函数将接受UserId而不是Int,并且创建UserId始终是显式的,如:

doSomething (UserId 12) (UseCaseId 15)

这类似于Niall C.创建包裹Int的类型的解决方案。但是,如果每种类型不需要10行就可以了。

答案 2 :(得分:2)

我想做一段相似的事情,你的帖子促使我尝试以下内容:

 public class Id<T> {
    private readonly int _Id;

    private Id(int id) {
        _Id = id;
    }

    public static implicit operator int(Id<T> id) {
        return id._Id;
    }

    public static implicit operator Id<T>(int id) {
        return new Id<T>(id);
    }
}

我可以使用如下

public void MyMethod(Id<UseCase> useCaseId)
{
   // Do something with the useCaseId
}

public void SomeOtherMethod()
{
   Id<User> userId = 12;
   Id<UseCase> useCaseId = 15;
   MyMethod(userId); // Compile error!!
}

我认为传递这种类型的Id对象比传递整个域对象更好,因为它使得调用者和被调用者之间的契约更加明确。如果只传递id,则知道被调用者没有访问该对象的任何其他属性。

答案 3 :(得分:1)

我个人认为没必要诚实 由开发人员正确实现逻辑,你不能依赖编译时错误来解决这些错误。

答案 4 :(得分:1)

游戏后期,但FWIW ...... this project on codeplex定义了几个“强类型”标量,如角度,方位角,距离,纬度,经度,弧度等。实际上,每个都是一个结构单个标量成员和几个方法/属性/构造函数来“正确”操作它。除了这些是值类型而不是引用类型这一事实之外,与将这些中的每一个都设为一类并没有太大的不同。虽然我没有使用该框架,但我可以看到可能使这些类型成为一等公民的价值。

不知道它是否最终是一个好主意,但是能够编写类似安全(和价值安全)的类似代码(类似于您的原始示例)似乎很有用:

Angle a = new Angle(45); //45 degrees
SomeObject o = new SomeObject();
o.Rotate(a); //Ensure that only Angle can be sent to Rotate

Angle a = (Angle)45.0;

Radians r = Math.PI/2.0;
Angle a = (Angle)r;

如果你的域有很多带有值语义的标量“类型”,并且可能有很多这些类型的实例,那么这个模式似乎最有用。将每个模型建模为具有单个标量的结构,可以提供非常紧凑的表示(比较每个都是完整的类)。虽然实现这种抽象级别(而不是仅仅使用“裸”标量来表示域值)和离散性可能有点痛苦,但结果API似乎更容易“正确”使用。

答案 5 :(得分:0)

  

我宁愿验证   MyMethod中的参数并加注   适用的例外情况   错误条件。

public void MyMethod(int useCaseId)
{
    if(!IsValidUseCaseId(useCaseId))
    {
         throw new ArgumentException("Invalid useCaseId.");
    }
    // Do something with the useCaseId
}

public bool IsValidUseCaseId(int useCaseId)
{
    //perform validation here, and return True or False based on your condition.
}

public void SomeOtherMethod()
{
    int userId = 12;
    int useCaseId = 15;
    MyMethod(userId); // Ooops! Used the wrong value!
}