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