在 C# 中实现幻像类型

本文关键字:类型 实现 | 更新日期: 2023-09-27 17:55:08

我希望使用"幻像类型"来实现类型安全的标识符。这里有一个关于在 F# 中执行此操作的问题。

我想在 C# 中执行此操作。如何?

我有一个解决方案(有问题),所以我会把它作为一个可能的答案发布,看看是否有人可以改进它。

在 C# 中实现幻像类型

为什么不让它成为一个带有构造函数私有的密封类呢?

public sealed class Id<TDiscriminator>
{
    private Id() { }
    //some static methods
}

我想出了以下内容:

struct Id<TDiscriminator>
{
    private readonly Guid _id;
    private Id(Guid id)
    {
        _id = id;
    }
    public Guid Value
    {
        get { return _id; }
    }
    public static Id<TDiscriminator> NewId()
    {
        return From(Guid.NewGuid());
    }
    public static Id<TDiscriminator> From(Guid id)
    {
        return new Id<TDiscriminator>(id);
    }
    public static readonly Id<TDiscriminator> Empty = From(Guid.Empty);
    // Equality operators ellided...
}

。我可以按如下方式使用:

class Order { /* empty */ }
class Customer { /* empty */ }
void Foo()
{
    var orderId = Id<Order>.NewId();
    var customerId = Id<Customer>.NewId();
    // This doesn't compile. GOOD.
    bool same = (orderId == customerId);
}

我不是特别想要鉴别器的具体类,因为我不希望任何人实例化它们。

我可以通过使用接口或抽象类来解决这个问题。 不幸的是,这些仍然可以派生自并实例化。

C# 不允许将静态类用作类型参数。我不能说我对这个问题的答案完全满意,因为答案基本上是"只是因为"。

怎么样?

public sealed class Order
{
    private Order() {}
}
public static sealed class Id<T>
{
    // ...
}

我想这正是你说的。没有人(除了一些特殊情况)可以构建它,也没有人可以从中继承。

好吧,据我所知,您希望提供一种通过自定义标识符对象区分不同类型的机制。我认为您几乎接近一个可行的解决方案。在具有泛型类的 .NET 中,泛型参数的每次替换(或泛型参数的每个唯一组合,如果有多个)都会在运行时中创建唯一类型。在代码中,Id<Order>Id<Customer>是两种不同的类型。NewId() 方法返回orderIdId<Order> 实例,并为customerId变量返回Id<Customer>。这两种类型不实现==运算符,因此无法进行比较。此外,这种比较将难以实现,因为您无法确定Id<TDsicriminator>的所有可能用途 - 您无法猜测TDsicriminator将被替换为哪种类型。

1

一个快速而简单的解决方案是这样做:

class Order { /* skipped */ }
class Customer { /* skipped */ }
void Foo()
{
    var orderId = Id<Order>.NewId();
    var customerId = Id<Customer>.NewId();
    bool sameIds = (orderId.Value == customerId.Value); // true
    bool sameObjects = orderId.Equals(customerId); // false
}

由于Value属性都是Guid类型,因此可以进行比较。

阿拉伯数字

但是,如果您需要实现==运算符,或对Id<TDisciminator>实例进行某种相等比较,则方法会有所不同。我想到的是以下内容:

public abstract class IdBase
{
    public abstract Guid Value { get; protected set; }
    public static bool operator == (IdBase left, IdBase right)
    {
        return left.Value == right.Value;
    }
}
public sealed class Id<TDiscriminator> : IdBase
{
   // your implementation here, just remember the override keyword for the Value property
}

但是,许多人不推荐第二种方法,因为IdBase的不同实现可能碰巧具有相同的Value属性(如果使用传递现有ID的构造函数)。例如:

var guid = Guid.NewGuid();
var customerID = Id<Customer>.From(guid);
var orderID = Id<Order>.From(guid);

在这里(客户ID == orderID)将返回true,这可能不是你想要的。

简而言之,在这种情况下,两种不同的类型将被视为相等,这是一个很大的逻辑错误,所以我坚持第一种方法。

如果你需要Id<Customer>.Value总是与Id<Order>.Value不同,因为泛型参数不同(Customer不同于Order),那么以下方法将起作用:

public sealed class Id<in TDiscriminator>
{
    private static readonly Guid _idStatic = Guid.NewGuid();
    private Id()
    {
    }
    public Guid Value
    {
        get { return _idStatic; }
    }
}

请注意此处使用的 in 关键字。这适用于 .NET 4.0,其中泛型可以是协变的,并确保类使用逆变泛型。(见 http://msdn.microsoft.com/en-us/library/dd469487.aspx)。在上面的代码中,_idStatic 字段对于作为泛型参数提供的每个不同类型的类型都有一个唯一的值。

我希望这些信息对您有所帮助。