Suena como lo que necesita es un tipo de interfaz, en lugar de un delegado. Los métodos de interfaz pueden aceptar tipos genéricos abiertos (que es lo que busca), aunque los delegados no puedan. Por ejemplo, se podría definir algo como:
interface ActOnConstrainedThing<CT1,CT2>
{
void Act<MainType>(MainType param) where MainType: CT1,CT2;
}
Incluso si los ejecutores de CT1
y CT2
no comparten un tipo de base común, que también implementa CT1
y CT2
, una implementación de Act
puede utilizar su parámetro pasado en una CT1
o CT2
sin typecast, e incluso podría pasarlo a rutinas que esperan un parámetro genérico con CT1
y CT2
restricciones. Tal cosa no sería posible con los delegados.
Tenga en cuenta que el uso de interfaces en lugar de delegados significa que no se puede usar el mecanismo y la sintaxis normal de "evento". En cambio, el objeto que sería un editor de eventos debe mantener una lista de instancias de objetos que implementan la interfaz deseada (por ejemplo, un List<ActOnConstrainedThing<IThis,IThat>>
) y enumerar las instancias en esa lista (quizás usando foreeach
). Por ejemplo:
List<IActOnConstrainedThing<IThis,IThat>> _ActOnThingSubscribers;
void ActOnThings<T>(T param) where T:IThis,IThat
{
foreach(var thing in _ActOnThingSubscribers)
{
thing.Act<T>(param);
}
}
Editar/Adición
El lugar en el que emplea este patrón también tenía algunas otras cosas que no parecía excesivamente relevante para la cuestión, que por mi interpretación fue preguntando cómo uno puede tener un delegado (o equivalente) con un parámetro de tipo abierto, de modo que el objeto que invoque el equivalente delegado pueda suministrar el parámetro de tipo, sin que el objeto suministre al delegado que debe conocerlo con antelación. La mayoría de los casos en que esto sea útil implican limitaciones genéricas, pero desde que la confusión aparentemente fue la introducción, aquí hay un ejemplo que no es así:
interface IShuffleFiveThings
{
void Shuffle<T>(ref T p1, ref T p2, ref T p3, ref T p4, ref T p5);
}
List<IShuffleFiveThings _ShuffleSubscribers;
void ApplyShuffles<T>(ref T p1, ref T p2, ref T p3, ref T p4, ref T p5)
{
foreach(var shuffler in _ShuffleSubscribers)
{
thing.Shuffle(ref p1, ref p2, ref p3, ref p4, ref p5);
}
}
El método IShuffleFiveThings.Shuffle<T>
toma cinco parámetros por ref
y hace algo con ellos (probablemente permuta de alguna manera, tal vez permutándolos al azar, o tal vez permutando algunos al azar mientras deja otros donde están.Si uno tiene una lista IShuffleFiveThings
, las cosas en esa lista se pueden usar de manera eficiente, sin boxeo o Reflexión, para manipular cualquier tipo de cosa (incluidos ambos tipos de clases y tipos de valores). Por el contrario, si se fuera a utilizar delegados:
delegate void ActOn5RefParameters(ref p1, ref p2, ref p3, ref p4, ref p5);
entonces porque cualquier instancia delegado particular sólo puede actuar sobre un solo tipo de parámetro suministrado en su creación (a menos que sea un delegado abierto que se llama sólo a través de la reflexión), uno necesitaría crear una lista separada de delegados para cada tipo de objeto que deseara mezclar (sí, sé que uno normalmente manejaría permutaciones usando una matriz de índices enteros; elegí la permutación como una operación porque es aplicable a todos los objetos tipos, no porque este método particular de permutando cosas es útil).
Tenga en cuenta que debido a que el tipo T
en IShuffleFiveThings
no tiene ninguna restricción, las implementaciones no podrán hacer mucho con él excepto por el encasillado (que puede presentar el boxeo). Agregar restricciones a dichos parámetros los hace mucho más útiles. Si bien sería posible codificar esas restricciones dentro de la interfaz, eso limitaría la utilidad de la interfaz para las aplicaciones que requieren esas limitaciones particulares. Hacer las restricciones en sí genéricas evita esa restricción.
El compilador no va a aceptar una propiedad con un tipo genérico desconocido que no se puede conocer hasta el tiempo de ejecución. – BoltClock
¿Qué esperas que signifique? – SLaks
Quiero pasar un parámetro genérico en el evento. Eso es todo. Seguramente, si supiera cuál sería el tipo T en tiempo de compilación, ¿no necesitaría usar genéricos en absoluto? – Xenoprimate