Универсальный C# с полями, не имеющими значения NULL, и полями, допускающими значение NULL

Мне нужен класс для обычного дерева во всем проекте. Иногда дерево используется со строковым ключом, а иногда с ключом Guid. Этот класс не должен иметь нулевой идентификатор и родительский идентификатор, допускающий значение NULL.

  1. Если я использую этот класс:
public class TreeNode<TKey> 
{
    public TKey Id { get; set; }
    public string Name { get; set; }
    public TKey? ParentId { get; set; }
}

1.а. TKey = Руководство. У меня возникает ошибка «CS0037: невозможно преобразовать значение null в «тип», поскольку это тип значения, не допускающий значения NULL», при попытке присвоить нулевое значение родительскому идентификатору.

1.б. ТКей = строка. В строковом ключе нет ничего плохого.

  1. Если я добавлю where TKey: struct:
public class TreeNode<TKey> where TKey : struct
{
    public TKey Id { get; set; }
    public string Name { get; set; }
    public TKey? ParentId { get; set; }
}

2.а. TKey = Руководство. Нет никакой ошибки.

2.б. ТКей = строка. У меня возникла ошибка «CS0453: тип «Имя типа» должен быть типом значения, не допускающим значения NULL, чтобы использовать его в качестве параметра «Имя параметра» в универсальном типе или методе «Универсальный идентификатор».

Как изменить класс, чтобы можно было использовать оба типа ключей?

Отредактировано: Спасибо за ответы. Подумал, может есть какой-нибудь лайфхак. Я решил выбрать 2 класса с базой:

public class NamedEntry<TKey> 
{
    public TKey Id { get; set; }
    public string Name { get; set; }
}

public class TreeNode<TKey> : NamedEntry<TKey> where TKey : struct
{
    public TKey? ParentId { get; set; }
}

public class TreeNodeNullable<TKey> : NamedEntry<TKey> where TKey : class
{
    public TKey? ParentId { get; set; }
}

Одним из вариантов может быть использование TKey = Guid? вместо Guid, и в этом случае ваш первый вариант скомпилируется и запустится; однако, возможно, было бы лучше переосмыслить дизайн.

Neil T 15.06.2024 14:43

это невозможно сделать, поскольку строка — это ссылочный тип, а GUID — это тип значения. Они плохо сочетаются друг с другом при указании ограничений на общие параметры. Не говоря уже о том, что типы значений, допускающие значение NULL, сильно отличаются от ссылочных типов, допускающих значение NULL, почти во всех отношениях, кроме представления C#.

Ivan Petrov 15.06.2024 19:31

Разве where TKey : IConvertible не подойдет? или может быть IComparable?

Poul Bak 15.06.2024 20:58
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Основная проблема заключается в том, что Guid — это тип значения, а string — ссылочный тип. Наличие универсального параметра либо значения, либо ссылочного типа не дает нам много места для маневра, когда дело доходит до творческого применения к нему ограничений (документы ).

Решение, которое я могу вам предложить, — это объявить универсальный тип структуры, который инкапсулирует ссылочный тип, например этот:

public struct StructOf<TClass>
where TClass : class {
    public TClass Instance;
    public StructOf(TClass instance) => Instance = instance;
    public static implicit operator StructOf<TClass>(TClass s) => new StructOf<TClass>(s);
    public static implicit operator TClass(StructOf<TClass> s) => s.Instance;
    public override string ToString() {
        return this.Instance.ToString();
    }
    // TODO: override Equals, GetHashCode etc., ==, !=
}

Теперь ваш исходный универсальный тип можно ограничить следующим образом:

public class TreeNode<TKey>
where TKey : struct {
    public TKey Id { get; set; }
    public string Name { get; set; }
    public TKey? ParentId { get; set; }
}

Таким образом, пример использования будет примерно таким (упрощенным благодаря перегрузке неявного оператора)

StructOf<string> foo = "foo"; // possible via implicit operator overload
Console.WriteLine(foo); // foo
string bar = foo; // possible via implicit operator overload
Console.WriteLine(bar); // foo

TreeNode<StructOf<string>> treeNode = new();
treeNode.Id = "foo"; // again implicit operator
treeNode.ParentId = null;

TreeNode<Guid> baz = new ();
baz.Id = new Guid();
baz.ParentId = null;

Другие вопросы по теме