与 C# 8.0 可空引用类型结合使用时,是否可以为值或字符串类型声明泛型类型约束?

Is it possible to declare a generic type constraint for value or string types when combined with C# 8.0 nullable reference types?

我已经写了两个抽象 classes 表示实体的基础 class:其中 Id 属性 是一个 int ,另一个允许使用通用类型参数 TId:

指定 Id 属性 的类型
/// <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; }
}

这些 classes 是在一个核心程序集中定义的,我几乎在我从事的所有项目中都使用它。自从 C# 8.0 发布以来,我尝试启用 nullable reference types,到目前为止效果很好。

但是在BaseEntity<TId>的情况下,编译器给出警告:

Non-nullable property 'Id' is uninitialized. Consider declaring the property as nullable.

我理解警告,但我似乎无法解决我的用例的问题。更具体地说,我想允许声明派生自:

由于 System.String 不是值类型,这似乎是不可能的:如果我将 TId 约束为结构 (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 可以是值类型(例如短、长、System.Guid、...)、OR一个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>,但任何时候你想要 构造 一个实体,你都需要在这两者之间进行选择.

我不知道这将如何与支持序列化联系起来,尽管我个人无论如何都会避开二进制序列化。