强类型整数

问题描述 投票:0回答:7

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

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!
}

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

为了以简单的方式解决这个问题,我尝试使用空枚举定义。有效地使用户 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!!
}

你觉得怎么样?

design-patterns strong-typing
7个回答
6
投票

如果您要麻烦地创建新类型来保存

UserId
UseCaseId
,您几乎可以轻松地将它们制作成简单的类,并使用
int
中的隐式转换运算符来为您提供所需的语法:

public class UserId
{
    public int Id { get; private set; }

    public UserId(int id)
    {
       id_ = id;
    }

    public static implicit operator UserId(int id)
    {
        return new UserId(id);
    }

    public static void Main()
    {
        UserId id1 = new UserId(1);
        UserId id2 = 2;
    }
}

这样,您就可以获得类型安全性,而不必在代码中使用强制转换。


2
投票

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

data UserId    = UserId    Int
data UseCaseId = UseCaseId Int

这样,函数将接受

UserId
而不是 Int,并且创建
UserId
始终是显式的,例如:

doSomething (UserId 12) (UseCaseId 15)

这类似于 Niall C. 的创建类型来包装 Int 的解决方案。然而,如果每个类型不需要 10 行来实现就好了。


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,您就知道被调用者没有访问对象上的任何其他属性。


1
投票

我个人觉得没必要说实话。
开发人员必须正确实现逻辑,不能依赖编译时错误来解决此类错误。


1
投票

游戏迟到了,但是 FWIW ... 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 似乎更容易“正确”使用。


0
投票

我宁愿验证 MyMethod 中的 argument 并提出 适当的例外情况 错误条件。

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!
}

0
投票

聚会已经很晚了,但我认为你强类型化这些概念而不是使用原语是正确的。

您可能会发现这个项目很有用:

https://github.com/SteveDunn/Vogen

© www.soinside.com 2019 - 2024. All rights reserved.