Почему C # избежал этого паттерна? И как бы вы это реализовали? - PullRequest
1 голос
/ 24 сентября 2010

КРАТКАЯ ФОРМА (!)

В ответ на комментарий Джона Скита я хочу, чтобы C # сделал так, чтобы обобщенный во время компиляции расширялся и наследовал один из его универсальных параметров (как демонстрирует Кирк Волл):

public class Generic<TBase> : TBase {

}

Этот класс, конечно, не сможет переопределить какие-либо члены TBase (если, возможно, если не будет применено ограничение) и должен будет повторить все открытые + защищенные конструкторы TBase без изменений - автоматически переадресовывать вызовы на базу.

Длинная форма вопроса показывает абстрактный + конкретный класс, который представляет общий шаблон для реализации данного интерфейса - но который, поскольку C # не допускает множественное наследование, не может быть легко применен поверх другого базового класса без клонирования весь код и прикрепление базового класса под абстракцией - не совсем СУХОЙ!

ДОЛГО ФОРМА

Я знаю технические причины, по которым следующий код не работает - генерики скомпилированы во время выполнения, а не шаблоны времени компиляции - но мне любопытно узнать, почему дизайнеры C # выглядят настолько сдержанными, чтобы идти по этому пути с язык (я видел цитируемость цитируется, но трудно понять это). Я также хотел бы знать, как вы, ребята, реализовали бы этот шаблон.

У меня есть интерфейс для объекта, который разрешает зависимости через IDependencyResolver:

public interface IDependant
{
  IDependencyResolver Resolver { get; set; }
}

У меня тогда есть абстрактный класс

public abstract class DependantBase : IDependant
{
  #region IDependant Members

  public abstract IDependencyResolver Resolver
  {
    get; set; 
  }

  #endregion

  public virtual TDependency ResolveDependency<TDependency>(
    string name, params object[] args)
  {
    //null checks elided
    return Resolver.Resolve<TDependency>(name, args);
  }

  public TDependency ResolveDependency<TDependency>(
    params object[] args)
  {
    return ResolveDependency<TDependency>(null, args);
  }
}

И, наконец, класс экземпляра, который просто материализует свойство Resolver:

public class Dependant : DependantBase
{
  public override IDependencyResolver Resolver
  { get; set; }
}

Таким образом, я могу получить этот тип и переопределить как свойство Resolver, так и способ выполнения операции разрешения.

Я использовал его на ванильном типе (то есть на другом, у которого нет базы), а затем я пришел написать MVC-контроллер.

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

//'injects' the DependantBase functionality
//between TBase and the deriving class
public abstract class DependantBase<TBase> : TBase, IDependant
{
  //as DependantBase above
}

//and then have Dependant<TBase>:
public class Dependant : DependantBase<TBase>
{ 
  //as Dependant above
}

Таким образом, если бы вышеупомянутое было возможно, я мог бы просто сделать это:

public class HomeController : Dependant<System.Web.Mvc.Controller>
{

}

А теперь HomeController наследуется от Dependant<System.Web.Mvc.Controller>, который, в свою очередь, наследуется от System.Web.Mvc.Controller.

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

Это явно не универсальный термин .Net, потому что я не уверен, как вы вообще начали бы представлять это в метаданных - но компилятор C # , безусловно, может воспринимать это как шаблон вместо этого и разверните его во время компиляции (боже, этого достаточно уже с деревьями выражений, интерфейсом DLR, встроенными делегатами, блоками итераторов и автоматически получить / установить свойства!).

Единственный способ увидеть что-то подобное - это вставить его в плагин Visual Studio (расширение нового языка или что-то в этом роде) или даже в текстовый шаблон.

Ответы [ 3 ]

2 голосов
/ 24 сентября 2010

Вы можете потенциально достичь своих целей, используя T4 .Это уже встроено в Visual Studio и работает с C # сегодня.

1 голос
/ 16 декабря 2012

Несмотря на то, что универсальные шаблоны C # являются шагом по сравнению с универсальными типами Java, объекты универсальных классов знают свои собственные типы, а универсальные методы знают параметры типов, с которыми они были вызваны, универсальные параметры типов не участвуют в привязках членов. Рассмотрим, например:

public class Foo1
{
    virtual public int Bar() { return 23; }
}
public class Foo2 : Foo1
{
    override public int Bar() { return 47; }
}
public class Foo3 : Foo2
{
    new public string Bar() { return "Meow"; }
}
public static class Tester
{
    public void Test<T,U>(T param1, U param2) where T:Foo1 where U:Foo3
    {
        var a=param1.Bar();
        var b=param2.Bar();
        Debug.Print("{0} {1}",a,b);
    }
    public static void Test()
    {
      var thing1 = new Foo3();
      Test(thing1, thing1);
    }
}

Обратите внимание, что параметры универсального типа T и U метода Test будут оба переданы Foo3, но param1.Bar() будет привязан к версии этого метода, существовавшей в Foo1 [ограничение type], тогда как param2.Bar() будет привязан к версии, существовавшей в Foo3. Поскольку Foo1.Bar() является виртуальным и переопределяется в Foo2, вызов param1.bar() даст 47. Обратите внимание, что не существует механизма, с помощью которого тип a может зависеть от универсального типа T, поэтому Компилятор никак не может использовать Foo3.Bar().

Если учесть, что привязки не могут быть выполнены на основе универсальных типов, а только на основе ограничений, наложенных на эти типы, будет ясно, что наследование от параметра универсального типа не может работать, поскольку одна из основных функций наследования, чтобы позволить производному классу использовать привязки от родителя. Если Generic<Base> должно быть производным от Base, это будет означать, что Generic<Foo1> должен иметь отличные привязки от Generic<Foo3>; такая функция просто недоступна.

Кстати, теоретически возможно и во многих случаях полезно реализовать конструкцию, в которой класс Foo<T> будет иметь член (например, Joe) некоторого типа интерфейса T и будет рассматриваться как реализующий этот интерфейс. Если бы Mark был типа Foo<IThingie>, тогда ((IThingie)Mark).DoSomething() вел бы себя как Mark.Joe.DoSomething() [но без Mark необходимости публичного показа Joe]. Это позаботится о большинстве случаев, когда будет полезно, чтобы тип наследовал от своих общих параметров. С помощью Reflection можно создавать типы, которые ведут себя таким образом во время работы программы, но нет никакого способа указать такие типы статически.

0 голосов
/ 24 сентября 2010

Итак, в основном вы хотите обернуть тип, которому нужны зависимости, в универсальный класс, который будет производным от его собственного универсального типа, и реализовать метод, который внедряет зависимости в базовый тип.Очевидно, что универсальный параметр TBase как родительский недопустим;вы не можете создать тип с родителем, который не известен во время разработки.C # - сильный статически типизированный язык.По сути, то, что вы хотите сделать, потребует динамической или «утки»;среда выполнения изучит потенциальное использование объекта, опросит сам объект и определит, является ли использование допустимым, основываясь на философии «если это выглядит как утка, крякает как утка ...».C # 4.0 может сделать это в некоторых случаях, используя новое ключевое слово «dynamic», но «динамический» не следует бросать вокруг, поскольку он в значительной степени отключает все проверки типов, которые компилятор может сделать для вас.

Если вы любите использовать универсальный для этой цели, вы можете приблизиться к этому, абстрагировавшись от базового типа, который будет использоваться как ограничение TBase:

public class DependantBase<TBase> : DependencyInjectableBase, IDependant where TBase: DependencyInjectableBase
{
...
}

Теперь DependantBase является производным от иработает с DependencyInjectableBase (который может быть пустым «меткой» родителя, если нет общей функциональности).Однако вы не можете объявить new DependantBase<ChildOfDependencyInjectableBase>() и рассматривать экземпляр DependantBase как производный объект ChildOfDependencyInjectableBase.Это часть основного наследования;класс не может рассматриваться как его «родной брат» или «двоюродный брат».

Метод, предоставляемый спецификацией языка C # для «mixins» (функциональность не определена в самом классе, но ее можно применять так, как если бы она была) это "методы расширения";публичные статические методы, которые задают первый параметр в качестве расширяемого типа с помощью контекстного ключевого слова this.Вы можете реализовать такой метод или методы для типов, в которые вы хотите внедрить зависимости:

public static InjectDependencies(this Object dependant)
{
   //reflectively examine properties of dependant's true type for dependency types
   //the resolver knows how to inject
}

Это может быть применено к любому объекту.Метод GetType () доступен для любого объекта .NET, и оттуда вы можете опросить типы свойств или полей, чтобы найти зависимости, которые вы можете разрешить.

...