Мне нужен класс для обычного дерева во всем проекте. Иногда дерево используется со строковым ключом, а иногда с ключом Guid. Этот класс не должен иметь нулевой идентификатор и родительский идентификатор, допускающий значение NULL.
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.б. ТКей = строка. В строковом ключе нет ничего плохого.
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; }
}
это невозможно сделать, поскольку строка — это ссылочный тип, а GUID — это тип значения. Они плохо сочетаются друг с другом при указании ограничений на общие параметры. Не говоря уже о том, что типы значений, допускающие значение NULL, сильно отличаются от ссылочных типов, допускающих значение NULL, почти во всех отношениях, кроме представления C#.
Разве where TKey : IConvertible
не подойдет? или может быть IComparable
?
Основная проблема заключается в том, что 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;
Одним из вариантов может быть использование
TKey
=Guid?
вместоGuid
, и в этом случае ваш первый вариант скомпилируется и запустится; однако, возможно, было бы лучше переосмыслить дизайн.