2008-09-03 21 views
57

He escuchado/leído el término pero no entiendo muy bien lo que significa.¿Despacho doble en C#?

¿Cuándo debería usar esta técnica y cómo la usaría? ¿Alguien puede proporcionar una buena muestra de código?

+0

Actualmente esta es la mejor manera de hacerlo: https://blogs.msdn.microsoft.com/curth/2008/11/15/c-dynamic-and-multiple-dispatch/ –

+0

Gran ejemplo de doble envío en [Aplicación de la composición de objetos para compilar modelos de dominio enriquecido] (https://vimeo.com/195774910). – jsuddsjr

+0

[Tenga cuidado con el doble despacho] (https://lostechies.com/derekgreer/2010/04/19/double-dispatch-is-a-code-smell/). Probablemente pueda evitarlo para un mejor mantenimiento del código. –

Respuesta

54

El patrón de visitante es una forma de hacer doble envío de una manera orientada a objetos.

Es útil para cuando quiere elegir qué método utilizar para un argumento dado en función de su tipo en tiempo de ejecución en lugar de tiempo de compilación.

El envío doble es un caso especial de envío múltiple.

Cuando llama a un método virtual en un objeto, eso se considera single-dispatch porque el método real se llama depende del tipo del único objeto.

Para el envío doble, se tienen en cuenta tanto el tipo de objeto como el tipo de argumento único del método. Esto es como la resolución de sobrecarga del método, excepto que el tipo de argumento se determina en tiempo de ejecución en doble despacho en lugar de estáticamente en tiempo de compilación.

En el despacho múltiple, un método puede tener múltiples argumentos pasados ​​y la implementación que se usa depende del tipo de cada argumento. El orden en que se evalúan los tipos depende del idioma. En LISP, comprueba cada tipo de principio a fin.

Los idiomas con despacho múltiple hacen uso de funciones genéricas, que son solo desviaciones de funciones y no son como métodos genéricos, que usan parámetros de tipo.

Para hacer doble despacho en C#, se puede declarar un método con un argumento único objeto y los métodos a continuación específicos con tipos específicos:

using System.Linq; 

class DoubleDispatch 
{ 
    public T Foo<T>(object arg) 
    { 
     var method = from m in GetType().GetMethods() 
        where m.Name == "Foo" 
         && m.GetParameters().Length==1 
         && arg.GetType().IsAssignableFrom 
              (m.GetParameters()[0].GetType()) 
         && m.ReturnType == typeof(T) 
        select m; 

     return (T) method.Single().Invoke(this,new object[]{arg});   
    } 

    public int Foo(int arg) { /* ... */ } 

    static void Test() 
    { 
     object x = 5; 
     Foo<int>(x); //should call Foo(int) via Foo<T>(object). 
    } 
}  
+2

Uso maravilloso de LINQ con reflexión: no había pensado aplicarlo a esos tediosos objetos xxxInfo anteriormente. ¡Gracias! –

+24

No estoy tan seguro de que esta sea una gran idea. Esto realmente no implementa el doble despacho, necesito saber * en compile-t * cuál es el tipo de cosa para especificar el parámetro de tipo. También puede no molestarse con todo el código de reflexión y simplemente lanzar el objeto. ¿Me estoy perdiendo de algo? – ljs

+4

Implementa el despacho doble en el sentido de que está distribuyendo tanto en el tipo de tiempo de ejecución del objeto (derivado de DoubleDispatch) como en el argumento del método. La reflexión sobre el tipo de devolución se usa para extender el machanismo a subclases, por lo que puede agregar "cadena Foo (cadena)" a una subclase y funcionará. –

11

Bueno hey chicos, el código publicado por Mark isnt completa y lo que no está funcionando.

Ajustado y completo.

class DoubleDispatch 
{ 
    public T Foo<T>(object arg) 
    { 
     var method = from m in GetType().GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic) 
        where m.Name == "Foo" 
          && m.GetParameters().Length == 1 
          //&& arg.GetType().IsAssignableFrom 
          //     (m.GetParameters()[0].GetType()) 
          &&Type.GetType(m.GetParameters()[0].ParameterType.FullName).IsAssignableFrom(arg.GetType()) 
          && m.ReturnType == typeof(T) 
        select m; 


     return (T)method.Single().Invoke(this, new object[] { arg }); 
    } 

    public int Foo(int arg) 
    { 
     return 10; 
    } 

    public string Foo(string arg) 
    { 
     return 5.ToString(); 
    } 

    public static void Main(string[] args) 
    { 
     object x = 5; 
     DoubleDispatch dispatch = new DoubleDispatch(); 

     Console.WriteLine(dispatch.Foo<int>(x)); 


     Console.WriteLine(dispatch.Foo<string>(x.ToString())); 

     Console.ReadLine(); 
    } 
} 

Gracias Mark y otros para la buena explicación sobre el patrón de doble Dispatcher

+0

Estoy de acuerdo. Este código está completo y muestra qué es el doble despacho. La explicación de la respuesta de Mark y este código van perfectamente juntas. –

+0

No escribiría "Console.WriteLine (dispatch.Foo (x));" ¿Será una forma más dinámica y útil de aprovechar el Patrón? –

0

C# 4 introduce el tipo pseudo dynamic la que se resuelve la llamada de función en tiempo de ejecución (en lugar de tiempo de compilación). (Es decir, se usa el tipo de tiempo de ejecución de la expresión). Double- (o multi-dispatch) se puede simplificar a:

class C { } 

static void Foo(C x) => Console.WriteLine(nameof(Foo)); 
static void Foo(object x) => Console.WriteLine(nameof(Object)); 

public static void Main(string[] args) 
{ 
    object x = new C(); 

    Foo((dynamic)x); // prints: "Foo" 
    Foo(x);   // prints: "Object" 
} 

Sin embargo, tenga cuidado con los tipos integrales. Como dynamic se trata como System.Object, nunca llamará a void Foo(int x) en el ejemplo anterior.

Tenga en cuenta también que al utilizar dynamic impide que el compilador analizadores estáticos examinen esta parte del código. Debe considerar cuidadosamente el uso dynamic.