C# 在C中强类型化ID值#

C# 在C中强类型化ID值#,c#,C#,有没有办法在C#中强键入整型ID值 我最近一直在使用Haskell,当应用于ID值时,我可以立即看到它的强类型的优点,例如,您永远不会希望使用PersonId代替ProductId 有没有一种很好的方法来创建一个可以用来表示给定类型Id的Id类/结构 我有以下想法,但不幸的是,它在许多层面上都不合法。您不能拥有抽象结构,并且隐式/显式强制转换运算符不会被继承 public abstract struct Id { int _value; public Id(int value)

有没有办法在C#中强键入整型ID值

我最近一直在使用Haskell,当应用于ID值时,我可以立即看到它的强类型的优点,例如,您永远不会希望使用PersonId代替ProductId

有没有一种很好的方法来创建一个可以用来表示给定类型Id的Id类/结构

我有以下想法,但不幸的是,它在许多层面上都不合法。您不能拥有抽象结构,并且隐式/显式强制转换运算符不会被继承

public abstract struct Id
{
    int _value;

   public Id(int value)
   {
      _value = value;
   }

   // define implicit Id to int conversion operator:
   public static implicit operator int(Id id) 
   {
      return _value;    
   }

   // define explicit int to Id conversion operator:
   public static explicit operator Id(int value) 
   {
      return new Id(value);
   }

   public bool Equals(object obj)
   {
      if(GetType() == obj.GetType()) 
      {
         Id other = (Id)obj;
         return other._value == _value;
      }
      return false;
   }

   public int GetHashCode()
   {
      return _value.GetHashCode();
   }
}

struct PersonId : Id { public PersonId(int value) : base(value) {} }
struct ProductId : Id { public ProductId(int value) : base(value) {} }

有没有有效的方法来执行类似的操作?否则,我们如何证明integer ID类型在大型应用程序中没有被混淆?

我不知道为什么您希望
PersonID
本身就是一个类,PersonID将是
int
类型的person的属性


Person.PersonID\\根据我的经验,在这种情况下,运算符重载通常不太有效,可能会导致各种问题。此外,如果您向类型
int
提供了隐式强制转换运算符,是否确定以下语句:

SomeCompany.ID = SomePerson.ID
编译器仍将捕获为无效?或者编译器可能只是使用您的强制转换运算符,从而允许无效赋值通过

不那么优雅的解决方案包括定义自己的值对象类型(作为
),并通过
属性访问实际ID:

sealed class PersonId
{
    readonly int value;
    public int Value { get { return value; } }

    public PersonId(int value)
    {
        // (you might want to validate 'value' here.)
        this.value = value;
    }

    // override these in order to get value type semantics:
    public override bool Equals(object other) { … }
    public override int GetHashCode() { … }
}
现在,无论何时编写
person.Id
,如果实际需要原始整数,就需要编写
person.Id.Value
。如果您实际尝试将对原始整数值的访问减少到尽可能少的位置,例如,将实体持久化到(或从)DB的位置,则效果会更好

p.S.:在上面的代码中,我真的想把
PersonId
a
struct
,因为它是一个值对象。但是,
struct
s有一个问题,即编译器自动提供的无参数构造函数。这意味着您的类型的用户可以绕过所有构造函数(可能会发生验证),并且您可能会在构造之后立即得到一个无效的对象。因此,尝试使
PersonId
尽可能类似于
结构
:通过声明它
密封
,通过重写
等于
GetHashCode
,以及不提供无参数构造函数

public interface IId { }

public struct Id<T>: IId {
    private readonly int _value;

    public Id(int value) {
        this._value = value;
    }

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

    public static explicit operator Id<T>(int value) {
        return new Id<T>(value);
    }
}

public struct Person { }  // Dummy type for person identifiers: Id<Person>
public struct Product { } // Dummy type for product identifiers: Id<Product>
显式运算符重载允许在id类型和基础id值之间安全轻松地强制转换。使用旧接口时,您可能希望将强制转换为整数更改为隐式,或者更好,以便使用正确类型的版本重载旧接口。当遗留接口来自第三方且不能直接更改或重载时,可以使用扩展方法

public interface ILegacy {
    public bool Remove(int user);
}

public static class LegacyExtensions {
    public static bool Remove(this ILegacy @this, Id<Person> user) {
        return @this.Remove((int)user);
    }
}
公共接口盲{
公共bool-Remove(int-user);
}
公共静态类LegacyExtensions{
公共静态bool Remove(this ILegacy@this,Id user){
返回@this.Remove((int)user);
}
}
编辑:根据smartcaveman的建议添加了
IId
界面


编辑:在考虑了Alejandro的建议后,将两个操作符都改为显式,并添加了一个如何处理遗留接口的部分。

我知道回答这个问题已经晚了(实际上是建议),但我考虑了这个问题,并想出了一个可能有用的主意

其基本思想是为每种类型创建一个标识工厂,返回实际id实例的接口

public interface IPersonId
{
    int Value { get; }
}

public static class PersonId
{
    public static IPersonId Create(int id)
    {
        return new Id(id);
    }

    internal struct Id : IPersonId
    {
        public Id(int value)
        {
            this.Value = value;
        }

        public int Value { get; }

        /* Implement equality comparer here */
    }
}
这需要一点工作,并且它不会与ORM一起工作,除非它能够访问内部的
结构
,所以除非这是一个问题,否则这个想法应该是安全的。 这是一个粗略的草案,我自己还没有完全测试过,但到目前为止,我得到了id的值类型,并且无参数的
struct
构造函数没有问题

public interface IId { }

public struct Id<T>: IId {
    private readonly int _value;

    public Id(int value) {
        this._value = value;
    }

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

    public static explicit operator Id<T>(int value) {
        return new Id<T>(value);
    }
}

public struct Person { }  // Dummy type for person identifiers: Id<Person>
public struct Product { } // Dummy type for product identifiers: Id<Product>
如果您愿意,解决方案也可以是通用的(灵感来自其他海报),如:

public interface IId<T>
{
    int Value { get; }
}

public static class Id
{
    public static IId<T> Create<T>(int value)
    {
        return new IdImpl<T>(value);
    }

    internal struct IdImpl<T> : IId<T>
    {
        public IdImpl(int value)
        {
            this.Value = value;
        }

        public int Value { get; }

        /* Implement equality comparer here */
    } 
}
公共接口IId
{
int值{get;}
}
公共静态类Id
{
公共静态IId创建(int值)
{
返回新的IdImpl(值);
}
内部结构IdImpl:IId
{
公共IdImpl(int值)
{
这个。值=值;
}
公共int值{get;}
/*在这里实现相等比较器*/
} 
}
然后您就可以执行
Id.Create(42)

就我个人而言,我不是这类仿制药的超级粉丝,但我想这真的是口味的问题。这实际上只是限制类型兼容性的一种方式,在我看来应该是明确的。 不过,这样做的好处是,你只能在一个地方进行平等比较,这是一个好的、干燥的地方

它绝对不是完美的,在大多数(如果不是大多数的话)用例中,它会完全过度设计,只是为了避开
struct
s有一个默认的无参数构造函数这一事实。
此外,由于明显的原因,无法对泛型变体进行验证检查,并且基础id值被限制为
int
(当然可以更改)。

您可以创建一个抽象基类,从中派生强类型标识符对象

public abstract class TypedGuid
{
    protected TypedGuid(Guid value)
    {
        Value = value;
    }

    public Guid Value { get; }
}
那么你继承了:

public class ProductId : TypedGuid
{
    public ProductId (Guid value) : base(value)
    { }
}
在抽象基类中,还可以实现
IEquatable
IFormattable
IComparable
接口。您还可以重写
GetHashCode
ToString
方法

public abstract class TypedGuid
{
    protected TypedGuid(Guid value)
    {
        Value = value;
    }

    public bool HasValue => !Value.Equals(Guid.Empty);
    public Guid Value { get; }

    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();
}
您还可以在类型化对象上提供静态方法,以方便地创建具有空值的实例

public class ProductId : TypedGuid
{
    public static ProductId Empty => new ProductId(Guid.Empty);
    public static ProductId New => new ProductId(Guid.NewGuid());

    public ProductId(Guid value) : base(value)
    {
    }
}
您还可以添加隐式运算符以允许隐式转换,但是这可能会击败robus
public class ProductId : TypedGuid
{
    public ProductId (Guid value) : base(value)
    { }
}
public abstract class TypedGuid
{
    protected TypedGuid(Guid value)
    {
        Value = value;
    }

    public bool HasValue => !Value.Equals(Guid.Empty);
    public Guid Value { get; }

    public override int GetHashCode() => Value.GetHashCode();
    public override string ToString() => Value.ToString();
}
public class ProductId : TypedGuid
{
    public static ProductId Empty => new ProductId(Guid.Empty);
    public static ProductId New => new ProductId(Guid.NewGuid());

    public ProductId(Guid value) : base(value)
    {
    }
}