2010-10-28 19 views
16

Hay dos descripciones de la delegada: en primer lugar, en un montaje de terceros:Cómo convertir un delegado a un delegado idéntico?

public delegate void ClickMenuItem (object sender, EventArgs e) 

en segundo lugar, la norma:

public delegate void EventHandler (object sender, EventArgs e); 

Estoy tratando de escribir un método que recibirá un parámetro de tipo EventHandler y llamará a una biblioteca de terceros, con el parámetro ClickMenuItem.

Cómo convertir el ClickMenuItem a EventHandler?

+0

artículo relacionado: http://msdn.microsoft.com/en-us/library/dd233060.aspx – ja72

Respuesta

22

Afortunadamente, es simple. Usted puede escribir:

ClickMenuItem clickMenuItem = ...; // Wherever you get this from 
EventHandler handler = new EventHandler(clickMenuItem); 

Y a la inversa:

EventHandler handler = ...; 
ClickMenuItem clickMenuItem = new ClickMenuItem(handler); 

Esto va a trabajar incluso en C# 1.0. Tenga en cuenta que si luego cambia el valor de la variable original, ese cambio no se reflejará en el en el "convertido". Por ejemplo:

ClickMenuItem click = new ClickMenuItem(SomeMethod); 
EventHandler handler = new EventHandler(click); 
click = null; 

handler(this, EventArgs.Empty); // This will still call SomeMethod 
+0

Pensando en ello, esto evitaría un nivel de indirección, ¿no es así? La nueva instancia 'Delegate' tendría el mismo' Target' y 'Method' que el original? – Ani

+0

@Ani: No estoy seguro. I * think * en realidad mantiene el delegado original, por lo que es una forma más simple de hacer su versión de expresión lambda. Aunque no estoy del todo seguro. –

+0

@Ani Cuando vea el código IL generado, puede ver que el nuevo delegado es una llamada al método 'Invoke' del antiguo delegado ... –

6

EDITAR: Hay una cuarta opción, es decir, evitar todas estas tonterías y hacer lo que Jon Skeet sugiere en su respuesta.

¿Algo como esto?

public static EventHandler ToEventHandler(this ClickMenuItem clickMenuItem) 
{ 
    if (clickMenuItem == null) 
     return null; 

    return (sender, e) => clickMenuItem(sender, e); 
} 

y lo contrario:

public static ClickMenuItem ToClickMenuItem(this EventHandler eventHandler) 
{ 
    if (eventHandler == null) 
     return null; 

    return (sender, e) => eventHandler(sender, e); 
} 

Tenga en cuenta que los compiladores infiere que delegan tipos para convertir las expresiones Lamda-a.

EDITAR: Si lo prefiere, también puede usar delegados anónimos.

EventHandler eventHandler = delegate(object sender, EventArgs e) 
          { 
           clickMenuItem(sender, e); 
          }; 
return eventHandler; // can be inlined, type-inference works fine 

La tercera alternativa, por supuesto, es escribir una clase de cierre usted mismo. Realmente no recomendaría esto, pero te da una idea de lo que hace el compilador con los métodos anónimos. Algo como:

public static class ClickMenuItemExtensions 
{ 
    public static EventHandler ToEventHandler(this ClickMenuItem clickMenuItem) 
    { 
     if (clickMenuItem == null) 
      return null; 

     // new EventHandler not required, included only for clarity 
     return new EventHandler(new Closure(clickMenuItem).Invoke); 
    } 

    private sealed class Closure 
    { 
     private readonly ClickMenuItem _clickMenuItem; 

     public Closure(ClickMenuItem clickMenuItem) 
     { 
      _clickMenuItem = clickMenuItem; 
     } 

     public void Invoke(object sender, EventArgs e) 
     { 
      _clickMenuItem(sender, e); 
     } 
    } 
} 
+0

¿Qué es "=>"? Proporcione un enlace a MSDN. No puedo encontrar. – SkyN

+0

@SkyN: Es el operador "goes to", que se usa para escribir una expresión lamda. http://msdn.microsoft.com/en-us/library/bb397687.aspx – Ani

+0

¿Por qué molestarse con todo esto cuando puede convertir directamente, según mi respuesta? –

0

Puede ser que desee pagar Variance in Delegates.

.NET Framework 3.5 y Visual Studio 2008 introdujo el soporte varianza para hacer coincidir firmas de los métodos con los tipos de delegados en todos los delegados en C# y Visual Basic. Esto significa que puede asignar a los delegados no solo métodos que tienen firmas coincidentes, sino también métodos que devuelven más tipos derivados (covarianza) o que aceptan parámetros que tienen menos tipos derivados (contravariancia) que los especificados por el tipo de delegado. Esto incluye delegados genéricos y no genéricos.

+0

Eso es extraño ... Podría haber jurado que esto era * en realidad * en C# 2 , no C# 3. –

+1

Por ejemplo, consulte http://en.csharp-online.net/New_Features_in_CSharp_2.0%E2%80%94Gain_Flexibility_with_Delegate_Covariance_and_Contravariance: parece que MSDN está equivocado ... –

+0

@Jon: * MSDN is wrong * (0: –

5

Además de otras respuestas, si usted quiere hacer la conversión entre tipos de delegado compatibles sin necesidad de conocer el tipo en tiempo de compilación, se puede hacer algo así:

static Delegate ConvertDelegate(Delegate sourceDelegate, Type targetType) 
{ 
    return Delegate.CreateDelegate(
      targetType, 
      sourceDelegate.Target, 
      sourceDelegate.Method); 
} 

Puede ser útil si necesidad de suscribirse a un evento de forma dinámica.

1

La respuesta de Thomas Levesque no funciona bien en algunos casos especiales. Esta es una versión mejorada.

public static Delegate ConvertDelegate(this Delegate src, Type targetType, bool doTypeCheck) 
{ 
    //Is it null or of the same type as the target? 
    if (src == null || src.GetType() == targetType) 
     return src; 
    //Is it multiple cast? 
    return src.GetInvocationList().Count() == 1 
     ? Delegate.CreateDelegate(targetType, src.Target, src.Method, doTypeCheck) 
     : src.GetInvocationList().Aggregate<Delegate, Delegate> 
      (null, (current, d) => Delegate.Combine(current, ConvertDelegate(d, targetType, doTypeCheck))); 
} 

El beneficio del código anterior es que pasa a la siguiente prueba

EventHandler e = (o,e)=>{} 
var a = e.ConvertDelegate(typeof(Action<object, EventArgs>), true); 
Assert.AreEqual(e, e.ConvertDelegate(typeof(EventHandler), true)); 

mientras

EventHandler e = (o,e)=>{} 
var a = new Action<object, EventArgs>(e); 
Assert.AreEqual(e, new EventHandler(a)); 

fallará.

Cuestiones relacionadas