2016-12-19 2 views
0

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

public abstract class ViewBase { } 

public abstract class ViewBase<T0, T1> : ViewBase 
{ 
    public abstract void Initialize(T0 arg0, T1 arg1); 
} 

public static class Navigator 
{ 
    public static void Navigate<TView, T0, T1>(T0 arg0, T1 arg1) where TView : ViewBase<T0, T1> 
    { 
     var view = (ViewBase<T0, T1>)Activator.CreateInstance<TView>(); 
     view.Initialize(arg0, arg1); 

     /* ... */ 
    } 
} 

выше код должен работать, но мне не нравится в длинный список общих типов каждый раз, когда я называю Navigate:

Navigate<DerivedView, string, int>("Joe", 42); 

Предполагая производный класс:

public class DerivedView : ViewBase<string, int> 
{ 
    /* ... */ 
} 

в genereal это может быть возможным уменьшить до:

Navigate<DerivedView>("Joe", 42); 

потому что информация типа излишняя.

Есть ли у вас какие-либо предложения? я пропускаю VARIADIC шаблоны;)

Благодаря

+1

родовых параметров для 'Initialize' является затенением параметров класса уровня' 'ViewBase так что вы, вероятно, хотите, чтобы удалить их. Затем вы можете изменить «Навигация» на «Навигация» (..), где TView: ViewBase , new() '. – Lee

+0

Вы правы, это была ошибка при написании сообщения. У меня нет общих аргументов в моем коде. Во всяком случае .. Это не настоящая проблема, просто нужно дать какой-то контекст. –

ответ

1

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

  • позволяют TView быть выведено из аргумента
  • избежать необходимости TView в качестве аргумента типа
  • избежать необходимости T0 и T1 в качестве аргументов типа
  • найти путь разделить аргументы типа

Вы можете позволить TView быть выведены путем передачи функции фабричной указать TView «s тип:

public static void Navigate<TView, T0, T1>(Func<TView> factory, T0 arg0, T1 arg1) 
     where TView : ViewBase<T0, T1> { 
    var view = factory(); 
    view.Initialize(arg0, arg1); 
    // ... 
} 

Теперь, вместо Navigate<DerivedView>("Joe", 42), называют Navigate(() => new DerivedView(), "Joe", 42).

Примечание: для этого потребуются типы arg0 и arg1, чтобы точно соответствовать TView. Неявные преобразования не будут работать. Если представление получено от ViewBase<int, object>, а arg1 - "Hello", то T1 будет выведено как тип string, вызывая сообщение об ошибке компилятора.


Поскольку вы не использовать TView «s типа, кроме как построить экземпляр, вы можете избежать TView в качестве аргумента типа, используя функцию заводской тоже:

public static void Navigate<T0, T1>(Func<ViewBase<T0, T1>> factory, T0 arg0, T1 arg1) { 
    var view = factory(); 
    view.Initialize(arg0, arg1); 
    // ... 
} 

Это также позволяет избежать проблем с неявными преобразованиями, поскольку и T0, и T1 могут быть выведены из factory.


Как избежать T0 и T1 в качестве аргументов типа: имя Initialize несколько предполагает, что его функциональные возможности могут принадлежать в конструкторе. Если это так, вы можете тривиально избегать их как аргументов типа, оставив конструкцию вызывающему: фабричная функция может быть () => new DerivedView("Joe", 42). Однако, если это не относится к вам, если вам действительно нужен отдельный метод Initialize, который вызывается Navigate, то я не вижу никаких параметров, которые избегают T0 и T1 как аргументы типа.


Наконец, разделив аргументы типа:

public static class Navigator { 
    public static WithViewHelper<TView> WithView<TView>() 
      where TView : new() => new WithViewHelper<TView>(); 
    public struct WithViewHelper<TView> where TView : new() { } 
} 
public static class NavigatorExtensions { 
    public static void Navigate<TView, T0, T1>(this Navigator.WithViewHelper<TView> withViewHelper, T0 arg0, T1 arg1) 
      where TView : ViewBase<T0, T1>, new() { 
     var view = new TView(); 
     view.Initialize(arg0, arg1); 
    } 
} 

Это позволяет вызывать Navigator.WithView<DerivedView>().Navigate("Joe", 42). Для этого требуется Navigate, поскольку в противном случае ограничение общего типа не может быть выражено. У него снова возникают проблемы с неявными преобразованиями: Navigator.WithView<DerivedView>().Navigate(null, 42) не сможет скомпилировать: хотя null конвертируется в string, компилятор не поймет, что T0 должен быть string.

+0

Спасибо за ваше расширенное сообщение. Думаю, я пойду за . последний вариант я изменил именование:. Navigator.To () .С («Джо», 42) и называется конструктор TView прямо в T () Метод Это делает второй вызов «с (...)' необязательный.До тех пор, пока решение AZ не станет возможным, я думаю, что это лучший вариант. –

0

Я предполагаю, что вы хотите это ограничение, которое сводится к тому:

class Generic<T> where T : Generic2<T1, T2> и имеют доступ к T1, T2 в ваших Generic подписей членов.

К сожалению, в настоящее время это не поддерживается в C#.

+0

Да, это было первое, что я имел в виду, тоже :( –

0

Помимо представленных подходов я придумал создания объекта из класса ViewBase:

public abstract class ViewBase<T0, T1> 
{ 
    public abstract void Initialize(T0 arg0, T1 arg1); 

    public static T Navigate<T>(T0 arg0, T1 arg1) where T : ViewBase<T0, T1> 
    { 
     var view = Activator.CreateInstance<T>(); 
     view.Initialize(arg0, arg1); 
     return view; 
    } 
} 

это позволяет следующий вызов:

DerivedView.Navigate<DerivedView>("Joe", 42); 

, но это также поставляется с избыточным кодом .. или :

ViewBase.Navigate<DerivedView>("Joe", 42); 
0

Рассмотрите также использование универсальной фабрики. Я кэширую сигнатуры вызовов конструктора для более быстрого поиска во второй раз.

public static class Factory 
{ 
    //store constructors for type T 
    static Dictionary<string, ConstructorInfo> ctors=new Dictionary<string, ConstructorInfo>(); 

    public static T New<T>(params object[] args) 
    { 
     var arg_types=args.Select((obj) => obj.GetType()); 
     var arg_types_names=args.Select((obj) => obj.GetType().Name); 

     string key=string.Format("{0}({1})", typeof(T).Name, string.Join(",", arg_types_names); 

     if(!ctors.ContainsKey(key)) 
     { 
      // if constructor not found yet, assign it 
      var ctor=typeof(T).GetConstructor(arg_types.ToArray()); 
      if(ctor==null) 
      { 
       throw new MissingMethodException("Could not find appropriate constructor"); 
      } 
      ctors[key]=ctor; 
     } 
     // invoke constructor to create a new T 
     return (T)ctors[key].Invoke(args); 
    }     
} 
Смежные вопросы