在C#中强烈输入ID值

chi*_*tom 22 c#

有没有办法在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) {} }
Run Code Online (Sandbox Code Playgroud)

是否有任何有效的方法来执行类似的操作?我们怎样才能证明整个ID类型不会在大型应用程序中混淆?

mgr*_*ber 21

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>
Run Code Online (Sandbox Code Playgroud)

现在你可以使用类型Id<Person>Id<Product>.的PersonProduct类型可以是结构或类.您甚至可以使用由id标识的实际类型,在这种情况下,您不需要任何虚拟类型.

public sealed class Person {
    private readonly Id<Person> _id;
    private readonly string _lastName;
    private readonly string _firstName;

    // rest of the implementation...
}
Run Code Online (Sandbox Code Playgroud)

显式运算符重载允许在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:添加IId了smartcaveman建议的界面.

编辑:在考虑了Alejandro的建议之后,将两个运算符更改为显式,并添加了如何处理传统接口的部分.

  • +1,我喜欢这个解决方案,但我认为可以做出两个改进.(1)使类实现一个接口`IId`,用于需要值的情况,但不希望耦合到正在识别的对象的类,(2)使它成为`Id <T ,TId>其中TId:struct`,因此允许您将该类用于其他标识值类型,如"long"或"Guid",这可能是一种可能的用法. (7认同)
  • @smartcaveman:`IId`界面是个好主意.但是,我不同意你的第二个建议.如果标识符类型的数量是合理的,我将为每个标识符定义一个新结构:`Id <T>,LongId <T>,GuidId <T>`.我会这样做,因为我更喜欢写'GuidId <Person>`而不是`Id <Person,Guid>`. (4认同)
  • 我喜欢这个选项,这可以避免一些错误源于拼写错误并与其他整数混合.不过我会建议一个小小的改变.我会切换隐式和显式运算符,以便隐式转换**到**`Id <T>`和显式**从**`Id <T>`到`int`,因为我发现更"危险" "打破这个结构提供的shell而不是意外地进入它,并防止这种混合是结构开始的主要目的.如果你想要int,请明确要求它,否则让编译器爆炸. (3认同)