generics - 与 C# 8.0 可空引用类型结合时,是否可以为值或字符串类型声明泛型类型约束?
问题描述
我编写了两个代表实体基类的抽象类:一个Id
属性是int,另一个允许Id
使用泛型类型参数指定属性的类型TId
:
/// <summary>
/// Represents the base class for all entities.
/// </summary>
[System.Serializable]
public abstract class BaseEntity
{
/// <summary>
/// Gets or sets the ID of the entity.
/// </summary>
public int Id { get; set; }
}
/// <summary>
/// Represents the base class for all entities that have an ID of type <typeparamref name="TId"/>.
/// </summary>
/// <typeparam name="TId">
/// The type of the <see cref="Id"/> property.
/// </typeparam>
[System.Serializable]
public abstract class BaseEntity<TId>
{
/// <summary>
/// Gets or sets the ID of the entity.
/// </summary>
public TId Id { get; set; }
}
这些类是在我从事的几乎所有项目中使用的核心程序集中定义的。自从 C# 8.0 出现以来,我已经尝试过启用可为空的引用类型,到目前为止效果很好。
但是,在 的情况下BaseEntity<TId>
,编译器会发出警告:
不可为空的属性“Id”未初始化。考虑将属性声明为可为空。
我理解这个警告,但我似乎无法为我的用例解决这个问题。更具体地说,我想允许声明派生自的类型:
System.String
, IEBaseEntity<string>
- 任何值类型,例如
BaseEntity<System.Guid>
或自定义结构
由于System.String
不是值类型,这似乎是不可能的:如果我限制TId
为 structs ( BaseEntity<TId> where TId : struct
),我就不能再声明BaseEntity<string>
了。
到目前为止,我发现禁用警告的唯一解决方案(?)是Id
使用其默认值初始化属性并使用!
运算符:
/// <summary>
/// Represents the base class for all entities that have an ID of type <typeparamref name="TId"/>.
/// </summary>
/// <typeparam name="TId">
/// The type of the <see cref="Id"/> property.
/// </typeparam>
[System.Serializable]
public abstract class BaseEntity<TId>
{
/// <summary>
/// Gets or sets the ID of the entity.
/// </summary>
public TId Id { get; set; } = default!;
}
但是,我想明确代码的意图:它TId
可以是一个值类型(例如 short、long System.Guid
、...),或者是一个System.String
.
这有可能吗?
解决方案
不,没有这样的约束——无论您是否使用可为空的引用类型。
您可能会做的是使用私有构造函数来确保只有在基类型中声明的类型才能派生自BaseEntity
,然后使用两个特定版本:
public abstract class BaseEntity<TId>
{
public TId Id { get; set; }
private BaseEntity<TId>(Id id) => Id = id;
public class StructEntity<T> : BaseEntity<T> where T : struct
{
public StructEntity() : base(default) {}
}
public class StringEntity : BaseEntity<string>
{
public StringEntity(string id) : base(id) {}
}
}
这仍然可以让您BaseEntity<T>
在大多数地方使用,但是任何时候您想要构建一个实体,您都需要在这两者之间进行选择。
我不知道这将如何与支持序列化联系起来,尽管我个人还是会避开二进制序列化。
推荐阅读
- firebase - Firebase 模块化 (v9) 与 Expo 不兼容
- tsql - Why does T-SQL think a an item is a column name?
- node.js - Nodejs TensorflowJS 达到 OOM 但堆只有 30mb
- flutter - 无法在初始化程序中访问实例成员“用户”。(扑)
- android - 从我的手机访问多个虚拟主机 IP 地址
- batch-file - 使用 sc 命令丢失 txt 文件的批处理脚本抛出错误
- vb.net - 如何在 vb.net 中使用时间的 Datediff?从开始时间到结束时间
- javascript - Firebase - 检查字段是否存在
- php - 编写一个简单的查询并解决: [Syntax Error] line 0, col 7: Error: Expected IdentificationVariable
- react-native - 将纳秒时间转换为 24 小时格式