¿Se puede pasar delegado genérico sin el parámetro de tipo?

Tengo tres proyectos

  1. Aplicación web MVC
  2. Aplicación de servicio que es una especie de dos capas de negocio / repository
  3. Entity framework (toda la configuración de EF vive aquí)

Referencias MVC > servicio

Servicio de referencias> EF

Tengo estos tres métodos actualmente que hacen algún trabajo.

public bool StoreUpload(UploadInformation information) where T : class, IUploadEntity { } public bool RemoveUpload(UploadInformation information) where T : class, IUploadEntity { } public bool CommitUpload(UploadInformation information) where T : class, IUploadEntity { } 

Llamo a estos tres métodos desde mi controlador usando estas interfaces que delegan a los métodos de trabajo anteriores:

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

Basándome en una condición de la enumeración UploadTypes en un conmutador, llamo al método de trabajo correcto. Hago esto porque no quiero que mi proyecto mvc tenga acceso a los tipos de base de datos de EF. De lo contrario, sé que alguien comenzará a consultar datos de toda la aplicación. Yo uso estas instrucciones de cambio para todos los métodos de interfaz:

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

Este método podría arrojar algo de luz sobre para qué se utilizan los parámetros de tipos. Estoy actualizando las tablas de forma genérica utilizando EF.

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

Sería bueno poder pasar el método de trabajo que requiere un parámetro de tipo como delegado a las llamadas del método de trabajo de conmutador.

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

Se puede hacer esto? Además, tuve problemas para construir un buen título para esta pregunta, así que por favor comente si esta es una mejor manera de describir el desafío.

No se puede hacer directamente por varias razones.

En primer lugar, como probablemente delegateMethod(information) notado, delegateMethod(information) simplemente no se comstack. Esto se debe a que en su ejemplo, el método delegateMethod es una variable local (el parámetro del método en realidad, pero sigue siendo una variable), y no puede aplicar “argumentos de tipo” a una variable; puede aplicarlos solo a un identificador que indique un genérico) tipo o un método (genérico).

La segunda razón es más interesante. Cuando pasa un método como delegado, el delegado realmente captura toda la firma del método, incluidos todos los tipos de parámetros.

 void Blah(UploadInformation information){ ... } var one = new Action(Blah); // -> Blah var two = new Action(Blah); // -> Blah var thr = new Action(Blah); // -> Blah MagicDoSomeWork(one, ...); // these all MagicDoSomeWork(two, ...); // delegates are already bound MagicDoSomeWork(thr, ...); // and remember their concrete T 

Debe especificar realmente el tipo para la Action para que se elija una versión correcta del método genérico de una descripción general llamada Blah . Estos delegates están obligados a versiones concretas del método y solo aceptarán esos tipos. Estos delegates están ‘cerrados’ en términos de sus argumentos de tipo. Usando formas normales, el MagicDoSomeWork simplemente no tendrá forma de alterar la T que estos delegates ya hayan recordado.

Que dos cosas son una especie de tapones de show, ya que solo por código normal, no se pueden escribir cosas como

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

ya que Action constructor simplemente requiere un parámetro de tipo. Y una vez que pase alguno, bloqueará los argumentos de tipo Blah.

Tampoco puedes usar delegates abiertos:

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

ya que el new operador requiere un tipo completo para crear un objeto.

Sin embargo, con un poco de vudú de reflexión, es posible analizar y construir dinámicamente un tipo genérico o un método genérico.

 // first, build the delegate in a normal way // and pick anything as the type parameters // we will later replace them var delegateWithNoType = new Action(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(upinfo) 

advertencia : este es un boceto para mostrarle cómo funciona el methodinfo delegate.Method/Target y methodinfo y getgenericdefinition and makegenericmethod . Lo escribí de memoria, nunca compilé, nunca corrí. Puede contener errores tipográficos menores, cosas pasadas por alto y unicornios de arco iris invisibles. No me di cuenta de ninguno. Probablemente porque eran invisibles.

Puedes hacerlo asi

 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; } } 

La enumeración solo será una lista de los métodos que quiero invocar de esta manera y creo una clase base para mis servicios, por lo que no tengo que pasar la instancia al método Invoke.

En lugar de usar delegates, considere usar una interfaz (o clase abstracta). De esta manera, sus métodos pueden conservar su naturaleza genérica.

Por ejemplo, si creas una interfaz como:

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

Tenga en cuenta que la T no está expuesta en el tipo, solo está en el método. Esta es la parte clave.

Ahora puedes implementar esto para tus métodos de base de datos:

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

Su método de conmutación / despacho puede verse así:

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

Y luego puedes escribir algo como:

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