Можно ли объявить обобщенное ограничение типа c для значений или строковых типов в сочетании с C# 8.0 обнуляемыми ссылочными типами? - PullRequest
4 голосов
/ 27 января 2020

Я написал два абстрактных класса, которые представляют базовый класс для сущностей: один, в котором свойство Id является int , другой, который позволяет указать тип свойства Id, используя Параметр типа generi c 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, то есть 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, ...), ИЛИ a System.String.

Возможно ли это как-то?

1 Ответ

3 голосов
/ 27 января 2020

Нет, такого ограничения нет - используете ли вы ссылочные типы, допускающие значение NULL, или нет.

То, что вы могли бы потенциально сделать, это использовать частный конструктор, чтобы убедиться, что только объявленные типы внутри базового типа может происходить от BaseEntity, а затем использовать две указанные c версии:

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> в большинстве мест, но в любое время, когда вы захотите построить сущность, вам нужно будет выбрать между этими двумя.

Я понятия не имею, как это повлияет на поддержку сериализации, хотя лично я буду держаться подальше двоичной сериализации в любом случае.

...