首页 > 解决方案 > 与 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不是值类型,这似乎是不可能的:如果我限制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.

这有可能吗?

标签: genericsc#-8.0nullable-reference-types

解决方案


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

可能会做的是使用私有构造函数来确保只有在基类型中声明的类型才能派生自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>在大多数地方使用,但是任何时候您想要构建一个实体,您都需要在这两者之间进行选择。

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


推荐阅读