【问题标题】:Is it possible to declare a generic type constraint for value or string types when combined with C# 8.0 nullable reference types?与 C# 8.0 可空引用类型结合时,是否可以为值或字符串类型声明泛型类型约束?
【发布时间】:2020-01-27 14:23:03
【问题描述】:

我编写了两个抽象类来表示实体的基类:一个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 出来后,我就尝试启用nullable reference types,到目前为止效果很好。

但是,对于BaseEntity&lt;TId&gt;,编译器会给出警告:

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

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

  • System.String,即BaseEntity&lt;string&gt;
  • 任何值类型,例如BaseEntity&lt;System.Guid&gt; 或自定义结构

由于System.String 不是值类型,这似乎是不可能的:如果我将TId 约束为结构(BaseEntity&lt;TId&gt; where TId : struct),我就不能再声明BaseEntity&lt;string&gt;

到目前为止,我发现禁用警告的唯一解决方案 (?) 是使用默认值初始化 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、...), System.String

这有可能吗?

【问题讨论】:

  • 顺便说一句,您可能希望禁止TId 的可空引用,您可以使用where TId : notnull 来禁止class Entity : BaseEntity&lt;string?&gt; 的使用。

标签: generics c#-8.0 nullable-reference-types


【解决方案1】:

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

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

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

【讨论】:

  • 感谢您的快速响应,也感谢您的 cmets 使用 System.Serializable 属性 @Jon Skeet!我会考虑删除它们。
猜你喜欢
  • 1970-01-01
  • 2023-03-17
  • 2020-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-21
相关资源
最近更新 更多