强类型整数

发布于 2024-09-13 20:33:23 字数 765 浏览 6 评论 0原文

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

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

您觉得怎么样?

As a thought experiment on a hobby project, I've been thinking of a way to ensure that this sort of subtle bug/typo doesn’t happen:

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

This bug would be hard to find because there’s no compile-time error, and you wouldn’t necessarily even get an exception at run-time. You’d just get "unexpected results".

To resolve this in a simple way, I’ve experimented with using empty enum definitions. Effectively making a user id a data type (without going quite as far as a class or a struct):

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

What d’you think?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

口干舌燥 2024-09-20 20:33:24

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

不知道这最终是否是一个好主意,但能够编写这样的代码(类似于您的原始示例)并确保类型安全(和值安全)似乎确实有用:

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 似乎更容易“正确”使用。

Late to the game, but FWIW ... this project on codeplex has defined several "strongly typed" scalars like Angle, Azimuth, Distance, Latitude, Longitude, Radian, etc. Actually, each of these is a struct with a single scalar member and several methods/properties/constructors to manipulate it "correctly". Not really much different than making each of these a class, aside from the fact that these are value types rather than reference types. While I have not used the framework, I can see the value of possibly making these types first class citizens.

Don't know whether it is ultimately a good idea or not, but it does seem useful to be able to write code like this (similar to your original example) with type safety (and value safety) ensured:

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

or

Angle a = (Angle)45.0;

or

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

It seems like this pattern would be most useful if your domain has a lot of scalar "types" with value semantics and potentially many instances of these types. Modeling each as a struct with a single scalar gives a very compact representation (compared making each a full blown class). While it might be somewhat of a pain to implement this level of abstraction (rather than just using "naked" scalars to represent domain values) and discreteness, the resultant API seems like it would be much easier to use "correctly".

枕梦 2024-09-20 20:33:24

我宁愿验证
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!
}

I'd rather prefer to validate the
argument
in MyMethod and raise
appropriate exception in case of
error-condition.

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!
}
烟雨凡馨 2024-09-20 20:33:24

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

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

https://github.com/SteveDunn/Vogen

Very late to the party, but I think you're right to strongly type such concepts instead of using primitives.

You might find this project useful:

https://github.com/SteveDunn/Vogen

痴者 2024-09-20 20:33:23

如果您在创建新类型来保存 UserIdUseCaseId 时遇到了麻烦,您几乎可以轻松地将它们制作成简单的类,并使用 UserIdUseCaseId 中的隐式转换运算符。 code>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;
    }
}

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

If you're going to the trouble of creating new types to hold UserId and UseCaseId, you could almost as easily make them into simple classes and use an implicit conversion operator from int to give you the syntax you want:

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

That way, you get the type safety without having to litter your code with casts.

梅窗月明清似水 2024-09-20 20:33:23

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

data UserId    = UserId    Int
data UseCaseId = UseCaseId Int

这样,函数将接受 UserId 而不是 Int,并且创建一个 UserId 总是显式的,类似于:

doSomething (UserId 12) (UseCaseId 15)

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

If it were Haskell and I wanted to do this, I might do it like:

data UserId    = UserId    Int
data UseCaseId = UseCaseId Int

This way, functions will accept a UserId instead of an Int, and creating a UserId is always explicit, something like:

doSomething (UserId 12) (UseCaseId 15)

This is similar to Niall C.'s solution of creating a type to wrap around an Int. However, it'd be nice if it didn't take 10 lines to implement per type.

甜妞爱困 2024-09-20 20:33:23

我想做类似的事情有一段时间了,你的帖子提示我尝试以下操作:

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

I have wanted to do something similar for a while and your post prompted me to try the following:

 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);
    }
}

which I can use as follows

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

I think passing this type of Id object is better than passing the entire domain object because it makes the contract more explicit between the caller and the callee. If you pass just the id, you know that the callee is not accessing any other property on the object.

清晰传感 2024-09-20 20:33:23

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

I personally think it is unnecessary to be honest.
It is down to the developer to implement the logic properly and you can not rely on compile time errors for such bugs.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文