2016-09-04 4 views
2

У меня есть три проектаМожете ли вы передать общий делегат без параметра типа?

  1. Веб-приложение MVC
  2. Service приложение, которое является своего рода два слоя бизнес/хранилище
  3. Entity Framework (все конфигурации EF живет здесь)

MVC ссылки> услуга

Услуга ссылки> EF

У меня есть эти три метода в настоящее время, которые выполняют некоторую работу.

public bool StoreUpload<T>(UploadInformation information) 
    where T : class, IUploadEntity { } 

public bool RemoveUpload<T>(UploadInformation information) 
    where T : class, IUploadEntity { } 

public bool CommitUpload<T>(UploadInformation information) 
    where T : class, IUploadEntity { } 

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

Boolean StoreUpload(UploadInformation information); 
Boolean RemoveUpload(UploadInformation information); 
Boolean CommitStoredDocuments(UploadInformation information); 

Исходя из условия из UploadTypes перечисления в коммутаторе я называю правильный метод работы. Я делаю это, потому что я не хочу, чтобы мой проект mvc имел доступ к типам базы данных EF, иначе я знаю, что кто-то начнет запрашивать данные со всего приложения. Я использую эти заявления переключатель для всех сопряженных методов:

public bool StoreUpload(UploadInformation information) 
{    
    switch (information.Type) 
    { 
     case UploadTypes.AutoIncident: 
      return RemoveUpload<AutoIncident>(information); 
     case UploadTypes.Incident: 
      return RemoveUpload<IncidentInjury>(information); 
     case UploadTypes.Inspection: 
      return RemoveUpload<Inspection>(information); 
     case UploadTypes.OtherIncident: 
      return RemoveUpload<OtherIncident>(information); 
     default: 
      return false; 
    } 
} 

public bool RemoveUpload(UploadInformation information) { ... } 
public bool CommitStoredUpload(UploadInformation information) { ... } 

Этот метод может пролить немного света на то, что параметры типов используются для. Я обновляю таблицы в общем виде с помощью EF.

private bool CommitStoredDocuments<T>(UploadInformation information) where T : class, IUploadEntity 
{ 
     var uploads = GetStoredUploads(information.UniqueId); 
     var entity = db.Set<T>().Include(e => e.Uploads) 
      .Single(e => e.UniqueId == information.UniqueId); 
     entity.Uploads.AddRange(uploads); 
... 

} 

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

public bool DoSomeWork(delegateMethod, information) { 
    switch(information.Type) { 
     case UploadTypes.AutoInciden: 
      return delegateMethod<AutoIncident>(information); 
     ... 
    } 
} 

Это можно сделать? Кроме того, мне не удалось создать хороший заголовок для этого вопроса, поэтому, пожалуйста, прокомментируйте, если это лучший способ описать проблему.

+0

Если метод DoSomeWork уже содержит ссылку на правильный метод (с использованием 'delegateMethod'), почему' s ведьма' снова? – haim770

+0

Я звоню из уровня, который не имеет доступа к этим типам. – jwize

ответ

2

Невозможно сделать это по нескольким причинам.

Прежде всего, как вы, вероятно, заметили, delegateMethod<FooBar>(information) просто не компилируется. Это связано с тем, что в вашем примере delegateMethod является локальной переменной (параметр метода фактически, но все еще переменной), и вы не можете применять «аргументы типа» <FooBar> к переменной - вы можете применять их только к идентификатору, который указывает (общий) типа или (общего) метода.

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

void Blah<T>(UploadInformation information){ ... } 

var one = new Action<int>(Blah); // -> Blah<int> 
var two = new Action<float>(Blah); // -> Blah<float> 
var thr = new Action<andsoon>(Blah); // -> Blah<andsoon> 

MagicDoSomeWork(one, ...); // these all 
MagicDoSomeWork(two, ...); // delegates are already bound 
MagicDoSomeWork(thr, ...); // and remember their concrete T 

Вы должны фактически указать тип для Action поэтому правильная версия универсального метода будут выбраны из общего описания называется Blah. Эти делегаты связаны с конкретными версиями метода и будут принимать только те типы. Эти делегаты «закрыты» в терминах аргументов их типа. Используя обычные способы, MagicDoSomeWork просто не сможет изменить T, который эти делегаты уже запомнили.

Это две вещи, которые являются своего рода шоу пробками, так как в обычном коде только, вы не можете писать такие вещи, как

var nope1 = new Action(Blah); // ctor for Action NEEDS type parameter 

поскольку конструктор Действие просто требует параметра типа. И как только вы пройти любого, он будет блокировать аргументы типа Ло

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

var nope1 = new Action<>(Blah); // can't use empty <> in this context :(

поскольку new оператор требует полного типа для создания объекта.

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

// first, build the delegate in a normal way 
// and pick anything as the type parameters 
// we will later replace them 
var delegateWithNoType = new Action<object>(Blah); 
// delegate has captured the methodinfo, 
// but uses a stub type parameter - it's useless to call it 
// but it REMEMBERS the method! 

// .... pass the delegate around 

// later, elsewhere, determine the type you want to use 
Type myRealArgument; 
switch(..oversomething..) 
{ 
    default: throw new NotImplemented("Ooops"); 

    case ...: myRealArgument = typeof(UploadTypes.AutoIncident); break; 
    ... 
} 

// look at the delegate definition 
var minfo = delegateWithNoType.Method; 
var target = delegateWithNoType.Target; // probably NULL since you cross layers 

var gdef = minfo.GetGenericDefinition(); 
var newinfo = gdef.MakeGenericMethod(myRealArgument); 

// now you have a new MethodInfo object that is bound to Blah method 
// using the 'real argument' type as first generic parameter 
// By using the new methodinfo and original target, you could now build 
// an updated delegate object and use it instead the original "untyped" one 
// That would be a NEW delegate object. You can't modify the original one. 

// ...but since you want to call the method, why don't use the methodinfo 

UploadInformation upinfo = ... ; 
newinfo.Invoke(target, new object[] { upinfo }); 
// -> will call Blah<UploadTypes.AutoInciden>(upinfo) 

слово предупреждения: это эскиз, чтобы показать вам, как delegate.Method/Target и methodinfo и getgenericdefinition и makegenericmethod работы. Я написал это из памяти, никогда не компилировался, никогда не работал. Он может содержать незначительные опечатки, пропущенные вещи и невидимые радужные единороги. Я этого не заметил. Наверное, потому что они были невидимы.

+0

Спасибо, это то, что я думал. Он выработает код. Кажется, это решение для этой задачи. У меня было это решение, но я хотел знать сначала. – jwize

+0

@jwize: Я немного отредактировал текст, чтобы использовать ваши имена в нескольких местах. Его должно быть проще читать. Кстати, название, которое вы выбрали, вполне нормально. Передача делегатов в таких случаях сложна. То, что я забыл написать, состоит в том, что на самом деле вам может не понадобиться делегат, особенно если вы закончите с «нулевыми» целями.Передача «голого» MethodInfos может быть намного легче понять для будущих мантеннеров. (contd) – quetzalcoatl

+0

@jwize: вы можете использовать делегатов, таких как Action/Func, для захвата методов, а затем, когда вы передаете их на другой уровень, вы можете выбрать их .Method, затем получить .GetGenericDefinition и передать **, что ** другой слой. Вы передадите объявление открытого универсального метода, но параметры типа еще не заполнены. Это было бы очень ясно «не использовать меня еще» для будущих читателей. Но хорошо, это выглядело бы более сложным, чем просто прохождение Action к слою ниже. Функционально то же самое (если значение .Target равно! = Null), так что выбирайте то, что вам больше нравится. – quetzalcoatl

0

Вы можете сделать это, как это

public bool Invoke(EntityType entityType, ActionType action, Object[] arguments) 
    { 
     var actionType = Enum.GetName(typeof(ActionType), action); 
     var type = GetType(); 
     var method = type.GetMethods().Single(m => m.IsGenericMethod && m.Name == actionType); 

     switch (entityType) 
     { 
      case EntityType.IncidentInjury: 
       var genericMethod = method.MakeGenericMethod(typeof(IncidentInjury)); 
       return (bool)genericMethod.Invoke(this, arguments); 
      default: 
       return false; 
     } 
    } 

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

0

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

Например, если вы создаете интерфейс, как:

interface IUploadAction 
{ 
    bool Perform<T>(UploadInformation information) 
     where T : class, IUploadEntity; 
} 

Обратите внимание, что T не подвергается в типе, это только на методе. Это ключевая часть.

Теперь вы можете реализовать это для ваших методов базы данных:

class CommitStoredDocuments : IUploadAction 
{ 
    public bool Perform<T>(UploadInformation information) 
     where T : class, IUploadEntity 
    { 
     var uploads = GetStoredUploads(information.UniqueId); 
     var entity = db.Set<T>().Include(e => e.Uploads) 
      .Single(e => e.UniqueId == information.UniqueId); 
     entity.Uploads.AddRange(uploads); 

     //... 
    } 
} 

Ваш коммутация/метод диспетчеризации может выглядеть следующим образом:

public bool DoAction(IUploadAction action, UploadInformation information) 
{ 
    switch (information.Type) 
    { 
     case UploadTypes.AutoIncident: 
      return action.Perform<AutoIncident>(information); 
     case UploadTypes.Incident: 
      return action.Perform<IncidentInjury>(information); 
     case UploadTypes.Inspection: 
      return action.Perform<Inspection>(information); 
     case UploadTypes.OtherIncident: 
      return action.Perform<OtherIncident>(information); 
     default: 
      return false; 
    } 
} 

И тогда вы можете написать что-то вроде:

IUploadAction storeUpload; 

public bool StoreUpload(UploadInformation information) => DoAction(storeUpload, information); 
+0

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

+0

@jwize Я неправильно понял, в чем проблема, я обновляю свой ответ ... это вообще помогает? – porges

+0

Да, вот как я это сделал. – jwize

Смежные вопросы