Ограничение базового класса для универсального класса, определяющего сам класс
Вчера я объяснял общие ограничения С# моим друзьям. При демонстрации ограничения where T : CLASSNAME
я взломал что-то вроде этого:
public class UnusableClass<T> where T : UnusableClass<T>
{
public static int method(T input){
return 0;
}
}
И был очень удивлен, увидев, что он скомпилирован. Однако, немного подумав, я понял, что это совершенно законно с точки зрения компилятора. UnusableClass<T>
- это такой же класс, как и любой другой, который можно использовать в этом ограничении.
Однако это оставляет пару вопросов: как этот класс можно использовать? Возможно ли
- Создать его?
- Наследовать от него?
- Вызовите его статический метод
int method
?
И если да, то как?
Если возможно какое-либо из них, каков будет тип T
?
Ответы
Ответ 1
Ну.
public class Implementation : UnusableClass<Implementation>
{
}
совершенно справедливо и, как таковая, делает
var unusable = new UnusableClass<Implementation>();
и
UnusableClass<Implementation>.method(new Implementation());
действует.
Итак, да, он может быть создан путем подачи наследующего типа в качестве параметра типа и аналогично вызову статического метода. Он, например, полезен для древовидных структур, где вы хотите в целом указать тип дочерних элементов, который имеет node, хотя он является одним и тем же типом.
Ответ 2
Этот подход широко используется в деревьях и других графоподобных структурах. Здесь вы говорите компилятору, что T имеет API UnusableClass. Тем не менее, вы можете реализовать TreeNode следующим образом:
public class TreeNode<T>
where T:TreeNode<T>
{
public T This { get { return this as T;} }
public T Parent { get; set; }
public List<T> Childrens { get; set; }
public virtual void AddChild(T child)
{
Childrens.Add(child);
child.Parent = This;
}
public virtual void SetParent(T parent)
{
parent.Childrens.Add(This);
Parent = parent;
}
}
И затем используйте его следующим образом:
public class BinaryTree:TreeNode<BinaryTree>
{
}
Ответ 3
Если возможно какое-либо из них, каков будет тип T?
Все они возможны, и именно вы определяете тип T
. Например, допустим, что существует тип, который наследуется от UnusableClass<T>
class Foo : UnusableClass<Foo> { }
Теперь вы можете создать экземпляр UnusableClass<Foo>
, потому что Foo
удовлетворяет ограничению:
UnusableClass<Foo> f = new UnusableClass<Foo>();
Тогда тип T
станет Foo
, и если вы попытаетесь позвонить method
, вам нужно передать экземпляр Foo
.