在C#中实现幻像类型

时间:2011-05-04 09:27:11

标签: c#

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

我想在C#中这样做。怎么样?

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

4 个答案:

答案 0 :(得分:4)

为什么不将它的构造函数设为私有的密封类?

public sealed class Id<TDiscriminator>
{
    private Id() { }

    //some static methods
}

答案 1 :(得分:1)

我想出了以下内容:

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#不允许您使用static class as a type argument。我不能说我对这个问题的答案完全满意,因为答案基本上都是“只是因为”。

答案 2 :(得分:0)

嗯,据我所知,您希望提供一种机制,通过自定义标识符对象区分不同类型。我认为你几乎接近一个有效的解决方案。在.NET中,当具有泛型类时,泛型参数的每次替换(或泛型参数的每个唯一组合,如果多于一个)在运行时中创建唯一类型。在您的代码Id<Order>Id<Customer>中有两种不同的类型。 NewId()方法为Id<Order>返回orderId的实例,为Id<Customer>变量返回customerId的实例。这两种类型没有实现==运算符,因此无法进行比较。此外,这种比较很难实现,因为您无法确定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类型,因此可以进行比较。

2

但是,如果您需要实现==运算符,或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);

此处(customerID == orderID)将返回true,这可能不是您想要的。

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

如果您需要Id<Customer>.Value始终与Id<Order>.Value不同,由于通用参数不同(CustomerOrder不同),则以下方法可行:

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字段对于作为泛型参数提供的每个不同类型都有一个唯一值。

我希望这些信息有用。

答案 3 :(得分:0)

怎么样?

public sealed class Order
{
    private Order() {}
}

public static sealed class Id<T>
{
    // ...
}

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