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

4

我编写了两个抽象类来表示实体的基类:一个其中 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; }
}

这些类定义在一个核心程序集中,我几乎在我所做的所有项目中都使用它们。自从 C# 8.0 发布以来,我一直在尝试启用 可空引用类型,到目前为止效果还不错。
然而,在 BaseEntity<TId> 的情况下,编译器会发出警告:

未初始化的非可空属性“Id”。考虑将该属性声明为可空。

我理解这个警告,但似乎无法解决我的问题。更具体地说,我想允许从以下类型派生的类型的声明:
  • System.String,即 BaseEntity<string>
  • 任何值类型,如 BaseEntity<System.Guid> 或自定义结构
由于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可以是值类型(例如short、long、System.Guid等),或者System.String

这是否可能?


顺便提一下,您可能希望禁止TId为空的引用,您可以使用where TId : notnull来禁止class Entity : BaseEntity<string?>这种用法。 - Orace
1个回答

3

不,没有这样的限制——无论您是否使用可空引用类型。

您可能需要使用私有构造函数来确保只有在基类型中声明的类型才能继承自 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>进行工作,但任何时候你想要构建一个实体时,都需要在两者之间做出选择。

我不知道这如何与支持序列化相关联,尽管我个人会避开二进制序列化。


谢谢您的快速回复,也感谢Jon Skeet关于使用System.Serializable属性的评论!我会考虑将它们删除。 - Steven Volckaert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接