2012-05-04 9 views
7

que tienen un delegado con un tipo genérico como uno de los parámetros:Eventos C# genéricos

public delegate void UpdatedPropertyDelegate<T>(
    RemoteClient callingClient, 
    ReplicableProperty<T> updatedProp, 
    ReplicableObject relevantObject 
); 

Ahora, yo quiero un evento público que puede ser suscrito por otras clases de usar. Por lo tanto, lo hice:

public event UpdatedPropertyDelegate<T> UpdatedProperty; 

Sin embargo, al compilador no le gusta eso. No entiendo por qué T debe especificarse aquí. Seguramente está especificado cuando dispare el evento, es decir:

if (UpdatedProperty != null) 
{ 
    UpdatedProperty(this, readProperty, 
     ReplicableObjectBin.GetObjectByID(readProperty.OwnerID)); 
} 

Entonces, ¿estoy haciendo algo simple mal? ¿O es esto una gran falla de comprensión?

Gracias.

+3

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

+0

¿Qué esperas que signifique? – SLaks

+0

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

Respuesta

7

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.

+0

Esto es interesante. En la pregunta de OP, ¿se implementaría esta interfaz por tipo que definía su evento o se implementaría por el tipo que se pasa en el evento? Básicamente, ¿cómo se usaría esto en la solución OP? – Jim

+0

@Jim: ver la edición anterior. Uno no puede usar eventos, pero uno puede lograr efectos similares con las interfaces.El patrón utilizado por 'IObservable' /' IObserver' puede ser una buena alternativa al uso de eventos. – supercat

+0

@supercate - A la derecha, obtengo el patrón de observador aquí, pero ¿el tipo adjunto no tendría que definir IThis, IThat type parameters o él mismo también especifica estos mismos parámetros de tipo? – Jim

4

En esencia, estás creando una instancia de ese delegado. Las instancias deben tener sus tipos genéricos definidos.

La definición de su delegado puede contener T, pero su instancia tiene que definir qué T.

+2

O estar en una clase genérica que define 'T' – payo

+0

Entonces, ¿hay alguna manera de pasar un parámetro genérico en un evento (sin especificar el tipo T en tiempo de compilación, lo que parece quitarme el sentido de un genérico?)? – Xenoprimate

+0

Es verdad, en ese caso, T está definido por la instancia de clase, por lo que la instancia de delegado también se define, lo que se cumple con lo que dije. –

3

Teniendo en cuenta este ejemplo:

public delegate void FooDelegate<T>(T value); 

public class FooContainer 
{ 
    public event FooDelegate<T> FooEvent; 
} 

El compilador como en el ejemplo no le gusta la declaración FooEvent porque T no está definido. Sin embargo, el cambio de FooContainer a

public delegate void FooDelegate<T>(T value); 

public class FooContainer<T> 
{ 
    public event FooDelegate<T> FooEvent; 
} 

Y ahora el compilador está bien con esto porque la persona que crea instancias de FooContainer ahora tendrá que especificar el tipo T al igual que

FooContainer<string> fooContainer = new FooFooContainer<string>(); 

Sin embargo también se puede restringir T a una interfaz como esta.

public delegate void FooDelegate<T>(T value) where T : IFooValue; 

public class FooContainer 
{ 
    public event FooDelegate<IFooValue> FooEvent; 

    protected void OnFooEvent(IFooValue value) 
    { 
     if (this.FooEvent != null) 
      this.FooEvent(value); 
    } 
} 

public interface IFooValue 
{ 
    string Name { get; set; }// just an example member 
} 

En este caso, puede provocar el evento con los tipos, siempre que implementan la interfaz IFooValue.