Сопоставление шаблонов для дженериков в неуниверсальном методе, реализующем интерфейс

У меня неприятная проблема. Я создаю механизм представления в ASP.NET MVC и реализую интерфейс IViewEngine. В одном из методов я пытаюсь динамически определить тип результата представления. Иногда результатом является шаблон (с типом Template‹'key>). Ключи используются для обозначения заполнителя в шаблоне, и идея состоит в том, чтобы использовать размеченное объединение, потенциально уникальное для каждого веб-сайта. Это может выглядеть так:

type MasterKey = | HeadContent | HeaderContent | MainContent | FooterContent
let MasterTemplate : Template<MasterKeys> = ...

Теперь проблема в следующем: поскольку я реализую интерфейс, я не могу контролировать сигнатуру метода. Поскольку я не могу добавить параметр общего типа, 'a будет преобразован в obj, и шаблон не будет соответствовать ниже:

   match result with
   | :? foo -> ...
   | :? bar -> ...
   | :? Template<'a> -> ...

Любые идеи?


person Johan Jonasson    schedule 04.07.2010    source источник


Ответы (2)


Можете ли вы сделать общий класс механизма просмотра в соответствии с типом 'key, который он использует? Индивидуальные проекты должны будут наследоваться от вашего класса движка представления и указывать тип ключа в процессе.

person Tim Robinson    schedule 04.07.2010
comment
Это оказалось отличной идеей во всей своей простоте! Я сделал, как вы сказали, добавив эту строку в Global.asax.cs: ViewEngines.Engines.Add(new WingBeats.Mvc.WingBeatsTemplateEngine‹MasterKey›()); Спасибо! (Я чувствую себя немного глупо, не придумать это решение самостоятельно, хотя я боролся часами!) - person Johan Jonasson; 06.07.2010

К сожалению, нет способа сделать это красиво. Если у вас есть контроль над типом Template<'T>, то лучший вариант — создать необобщенный интерфейс (например, ITemplate) и реализовать его в типе Template<'T>. Затем вы можете просто проверить интерфейс:

| :? ITemplate as t -> ...

Если это не так, то ваш единственный вариант — использовать магию отражения. Вы можете реализовать активный шаблон, который соответствует, когда тип является некоторым Template<'T> значением, и возвращает список типов (System.Type объектов), которые использовались в качестве универсальных аргументов. В вашем псевдокоде вы хотели получить это как параметр универсального типа 'a - это невозможно получить как параметр типа времени компиляции, но вы можете получить это как информацию о типе времени выполнения:

let (|GenericTemplate|_|) l =
  let lty = typeof<list<int>>.GetGenericTypeDefinition()
  let aty = l.GetType()
  if aty.IsGenericType && aty.GetGenericTypeDefinition() = lty then
    Some(aty.GetGenericArguments())
  else 
    None

Теперь вы можете написать следующий код сопоставления с образцом:

match result with
| GenericTemplate tys ->
    // ...

Последняя проблема заключается в том, как вы можете использовать эту информацию о типе среды выполнения для запуска некоторого универсального кода. Лучший вариант, который я могу придумать, - это вызвать универсальный метод (или функцию) с использованием отражения - тогда вы можете указать информацию о типе среды выполнения в качестве универсального параметра, и поэтому код может быть универсальным. Самый простой вариант — вызвать статический член типа:

 type TemplateHandler =
   static member Handle<'T>(arg:Template<'T>) =
     // This is a standard generic method that will be 
     // called from the pattern matching - you can write generic
     // body of the case here...
     "aaa"

| :? GenericTemplate tys ->
    // Invoke generic method dynamically using reflection
    let gmet = typeof<TemplateHandler>.GetMethod("Handle").MakeGenericMethod(tys)
    gmet.Invoke(null, [| result |]) :?> string // Cast the result to some type

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

Вы также можете изменить код, чтобы использовать функцию let вместо static member — лишь немного сложнее найти функцию с использованием отражения.

person Tomas Petricek    schedule 04.07.2010
comment
Мне нравится этот подход, но вы можете сделать активный шаблон немного более общим, параметризировав его на основе определения универсального типа для соответствия. См. stackoverflow.com/questions/2140079/. - person kvb; 04.07.2010
comment
@kvb: Хороший трюк! Я попытался написать общий активный шаблон, но кажется, что нет возможности указать параметр типа в использовании. Использование кавычек, кажется, работает хорошо (или, возможно, параметр может быть просто (typedefof<Template<_>>)?) - person Tomas Petricek; 04.07.2010
comment
В шаблонах разрешено только ограниченное подмножество языковых элементов (даже для параметров активных шаблонов), поэтому я не думаю, что вы можете напрямую передать тип. Насколько я могу судить, использование кавычек — самое чистое решение, разрешенное языком, но я бы хотел, чтобы меня опровергли! - person kvb; 04.07.2010
comment
Хорошие идеи! Хотя в данном конкретном случае оказалось, что это вуду мне и не нужно. Спасибо, в любом случае! Я ценю все усилия. - person Johan Jonasson; 06.07.2010