Ограничение базового класса для универсального класса, определяющего сам класс

Вчера я объяснял общие ограничения С# моим друзьям. При демонстрации ограничения 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.