Рекурсивные ограничения: Что означает DBase <T>: где T: DBase <T>? - PullRequest
0 голосов
/ 31 декабря 2018

Я думал, что понял общие ограничения, пока не наткнулся на это.

public class DBase<T> : DbContext, IDisposable where T : DBase<T>

Как T может быть DBase<T>?
И если это возможно, что это значит?
Этот код компилируется и работает нормально.Я не решаю проблему.Я просто не понимаю этого.

Это используется здесь

    public class ChildDb : DBase<ChildDb>

Что, опять же, не для меня.Он передает себя как параметр типа?

Ответы [ 3 ]

0 голосов
/ 31 декабря 2018

Джон Ву опубликовал отличный блог в комментариях, TLDR которого:

Этот шаблон кода позволяет вам объявить суперкласс, который должен быть расширен (возможно, не вами, если выпереписываете библиотеку, которую будут использовать другие люди) для использования, но может иметь несколько методов / подписей (написано вами) , которые возвращают T, когда вы пишете их, но на практикевернуть объекты дочернего типа (не написанные вами / вы не можете знать) , чтобы их можно было использовать в цепочке (например, как большинство методов StringBuilder возвращают сам StringBuilder, чтобы пользователь мог вызвать .Append () .AppendLine ()) без необходимости приведения (в коде, который вы не написали) из родительского типа (написанный вами) в дочерний тип (ненаписано вами)

Есть одна оговорка: она не особенно полезна, потому что может быть создан только самый глубокий потомок в дереве наследования.Избегайте его использования

0 голосов
/ 31 декабря 2018

В качестве полезного примера, он позволяет вам иметь некоторые методы или свойства в базовом классе, которые возвращают производный тип.

Например, в беглых компоновщиках, которые имеют цепочечные методы, скажем, у нас есть базастроитель, который устанавливает некоторые общие свойства.Каким должен быть тип вывода этих методов?

См. Следующий пример:

public abstract class Control
{
    public string Id { get; set; }
}
public abstract class ControlBuilder<TBuilder, TControl>
    where TBuilder : ControlBuilder<TBuilder, TControl>, new()
    where TControl : Control, new()
{
    protected TControl control;
    protected ControlBuilder()
    {
        control = new TControl();
    }
    public static TBuilder With()
    {
        return new TBuilder();
    }
    public TControl Build()
    {
        control;
    }
    public TBuilder Id(string id)
    {
        control.Id = id;
        return (TBuilder)this;
    }
}

Не имея ControlBuilder<TBuilder, TControl> в качестве ограничения для TBuilder, как вы можете вернуть TBuilder из Id метода?

Если вы спросите, почему бы не вернуть ControlBuilder<TBuilder, TControl>, потому что, если вы вернете его после вызова .Id ("что-то") в цепочках методов, он не будет показывать методы производного класса, а просто покажет методыControlBuilder<TBuilder, TControl>.

Допустим, мы создаем TextBoxBuilder для построения TextBox:

public class TextBox : Control
{
    public string Text { get; set; }
}
public class TextBoxBuilder : ControlBuilder<TextBoxBuilder, TextBox>
{
    public TextBoxBuilder Text(string text)
    {
        control.Text = text;
        return this;
    }
}

Теперь мы можем использовать его, как и ожидалось:

var txt = TextBoxBuilder.With().Id("textBox1").Text("Hello!").Build();
0 голосов
/ 31 декабря 2018

Как T может быть DBase<T>?

Нет ограничений, которые не позволяют генерировать общий параметр из себя.Хотя это не совсем понятно с приведенным вами примером.Как насчет Вершины / Вершины ?

Выдержка из Википедии:

enter image description here

В геометрии,вершина (множественное число: вершины или вершины) - это точка, где встречаются две или более кривых, линий или ребер.Как следствие этого определения, точка, где две линии встречаются, образуя угол, а углы многоугольников и многогранников являются вершинами. 1

Как описать вершину (точка)?

// very simplified example
public class Vertex
{
  public int X { get; set; }
  public int Y { get; set; }
}

Теперь, как мы можем добавить коллекцию связанных вершин в этот класс, но разрешить только то, что происходит от этого класса?

public class Vertex<TVertex> : Vertex
  where TVertex : Vertex<TVertex>
{
  public IEnumerable<TVertex> Vertices { get; set; }
}

Это универсальная версияскажем:

public Vertex2
{
  public IENumerable<Vertex2> Vertices { get; set; }
}

Однако, когда я наследую от Vertex2, мой Vertices всегда должен быть IEnumerable<Vertex2>, и правильный способ разрешить Vertices быть производным классом - это использовать этот тип self- ссылка общая.

Извините, Эрик, я потерял смысл в деталях.Что я получил от рекурсии?

При использовании Vertex2 наши производные типы теряют доступ к другим производным свойствам:

public class MyVertex2: Vertex2
{
  public int Id { get; set; }
}

, поэтому

var a = new MyVertex2 {Id = 1 };
var b = new MyVertex2 { Id = 2 };
a.Vertices = new List<Vertex2> { b };
b.Vertices = new List<Vertex2> { a };

// can't access Id because it's a Vertex2 not a MyVertex2
var bId = a.Vertices.First().Id;

Конечновы можете разыграть его, но затем вы разыгрываете его повсюду (это не DRY ) ... а что если это не MyVertex (MullReferencesException или InvalidCastException).

public class MyVertex: Vertex<MyVertex>
{
  public int Id { get; set; }
}
var a = new MyVertex {Id = 1 };
var b = new MyVertex { Id = 2 };
a.Vertices = new List<MyVertex > { b };
b.Vertices = new List<MyVertex > { a };

var bId = a.Vertices.First().Id;
// or even
var aId = a.Vertices.First().Vertices.First();

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...