2010-03-17 34 views
6

Actualmente tengo 2 métodos concretos en 2 clases abstractas. Una clase contiene el método actual, mientras que la otra contiene el método heredado. P.ej.delegado C# y clase abstracta

// Class #1 
public abstract class ClassCurrent<T> : BaseClass<T> where T : BaseNode, new() 
{ 
    public List<T> GetAllRootNodes(int i) 
    { 
     //some code 
    } 
} 

// Class #2 
public abstract class MyClassLegacy<T> : BaseClass<T> where T : BaseNode, new() 
{ 
    public List<T> GetAllLeafNodes(int j) 
    { 
     //some code 
    } 
} 

Quiero que el método correspondiente se ejecute en sus escenarios relativos en la aplicación. Estoy planeando escribir un delegado para manejar esto. La idea es que puedo llamar al delegado y escribir la lógica para manejar qué método llamar según la clase/proyecto desde el que se llama (al menos para eso creo que son los delegados y cómo se usan).

Sin embargo, tengo algunas preguntas sobre ese tema (después de algunas google):

1) ¿Es posible tener un delegado que conoce las 2 (o más) los métodos que residen en diferentes clases? 2) ¿Es posible crear un delegado que genere clases abstractas (como en el código anterior)? (Supongo que no, ya que los delegados crean una implementación concreta de las clases aprobadas) 3) Intenté escribir un delegado para el código anterior. Pero estoy siendo técnicamente reté:

public delegate List<BaseNode> GetAllNodesDelegate(int k); 
    GetAllNodesDelegate del = new GetAllNodesDelegate(ClassCurrent<BaseNode>.GetAllRootNodes); 

me dieron el siguiente error:

An object reference is required for the non-static field, method, property ClassCurrent<BaseNode>.GetAllRootNodes(int) 

que podría haber entendido algo ... pero si tengo que declarar manualmente un delegado en la clase de llamada, Y para pasar la función manualmente como se indicó anteriormente, entonces empiezo a cuestionar si el delegado es una buena manera de manejar mi problema.

Gracias.

Respuesta

7

La forma en que está tratando de utilizar delegados (construyéndolos con new, declarar un tipo de delegado nombrado) sugiere que usted está usando C# 1. Si en realidad estás usando C# 3, es mucho más fácil que eso.

En primer lugar, el tipo de delegado:

public delegate List<BaseNode> GetAllNodesDelegate(int k); 

ya existe. Es solo:

Func<int, List<BaseNode>> 

Así que no necesita declarar su propia versión de la misma.

En segundo lugar, debe pensar en un delegado como si fuera una interfaz con solo un método, y puede "implementarlo" sobre la marcha, sin tener que escribir una clase con nombre. Simplemente escriba una lambda, o asigne un nombre de método directamente.

Func<int, List<BaseNode>> getNodesFromInt; 

// just assign a compatible method directly 
getNodesFromInt = DoSomethingWithArgAndReturnList; 

// or bind extra arguments to an incompatible method: 
getNodesFromInt = arg => MakeList(arg, "anotherArgument"); 

// or write the whole thing specially: 
getNodesFromInt = arg => 
    { 
     var result = new List<BaseNode>(); 
     result.Add(new BaseNode()); 
     return result; 
    }; 

A lambda es de la forma (arguments) => { body; }. Los argumentos están separados por comas. Si solo hay uno, puede omitir los paréntesis. Si no requiere parámetros, coloque un par de paréntesis vacíos: (). Si el cuerpo tiene una sola declaración de longitud, puede omitir las llaves. Si se trata de una sola expresión, puede omitir las llaves y la palabra clave return. En el cuerpo, puede referirse prácticamente a cualquier variable y método del ámbito adjunto (aparte de los parámetros ref/out del método adjunto).

Casi nunca hay necesidad de usar new para crear una instancia de delegado.Y rara vez es necesario declarar tipos de delegados personalizados. Use Func para los delegados que devuelven un valor y Action para los delegados que devuelven void.

Cuando lo que necesita para pasar es como un objeto con un método (ya sea una interfaz o una clase), utilice un delegado en su lugar, y podrá evitar mucho desorden.

En particular, evite definir interfaces con un método. Simplemente significa que en lugar de ser capaz de escribir un lambda para implementar ese método, que tendrá que declarar una clase llamada separada para cada aplicación diferente, con el patrón:

class Impl : IOneMethod 
{ 
    // a bunch of fields 

    public Impl(a bunch of parameters) 
    { 
     // assign all the parameters to their fields 
    } 

    public void TheOneMethod() 
    { 
     // make use of the fields 
    } 
} 

Un lambda efectivamente hace todo lo para ti, eliminando tales patrones mecánicos de tu código. Usted acaba de decir:

() => /* same code as in TheOneMethod */ 

También tiene la ventaja de que se puede actualizar las variables en el ámbito de inclusión, porque se puede hacer referencia directamente a ellos (en lugar de trabajar con los valores copiados en los campos de una clase). A veces esto puede ser una desventaja si no desea modificar los valores.

+0

Si desea que el delegado se asigne a un método específico si es de tipo 'ClassCurrent' (' GetAllRootNodes') y otro método si es de tipo 'MyClassLegacy' (' GetAllLeaveNodes'), entonces debería saber el tipo de la instancia. Mientras que si comparte una interfaz común, no es necesario conocer el tipo simplemente porque implementa la interfaz dada. El código para configurar el delegado para cada tipo contendrá un if para verificar el tipo o un método que tome el tipo específico como argumento para cada tipo permitido. – Cornelius

+0

@ Cornelius: ¿qué crees que es un delegado? Parece que piensas que es lo mismo que un puntero de función en C. –

+2

Los delegados son más poderosos que los punteros de función, ya que son "métodos de primera clase" y tienen cierre, pero esto solo te permitirá pasarlos polimórficamente y no elimine la complejidad de configurarlo como en el comentario anterior. – Cornelius

1

Puede tener un delegado que se inicializa con referencias a diferentes métodos dependiendo de algunas condiciones.

En relación con sus preguntas:
1) No estoy seguro de lo que quiere decir con "sabe". Puede pasar cualquier método al delegado, por lo tanto, si puede escribir un método que "sepa" sobre algunos otros métodos, puede hacerlo con un delegado similar.
2) De nuevo, los delegados se pueden crear desde cualquier método que se pueda ejecutar. Por ejemplo, si tiene una variable local inicializada de tipo ClassCurrent<T>, puede crear un delegado para cualquier método de instancia del tipo ClassCurrent<T>.
3) El delegado puede llamar solo al método que realmente se puede llamar. Quiero decir que no puede llamar al ClassCurrent.GetAllRootNodes porque GetAllRootNodes no es un método estático, por lo que necesita una instancia del ClassCurrent para llamarlo.

El delegado puede permanecer en cualquier clase que tenga acceso al ClassCurrent y MyClassLegacy.

Por ejemplo, puede crear algo bajo como:

class SomeActionAccessor<T> 
{ 
    // Declare delegate and fied of delegate type. 
    public delegate T GetAllNodesDelegate(int i); 

    private GetAllNodesDelegate getAllNodesDlg; 

    // Initilaize delegate field somehow, e.g. in constructor. 
    public SomeActionAccessor(GetAllNodesDelegate getAllNodesDlg) 
    { 
     this.getAllNodesDlg = getAllNodesDlg; 
    } 

    // Implement the method that calls the delegate. 
    public T GetAllNodes(int i) 
    { 
     return this.getAllNodesDlg(i); 
    } 
} 

Los delegados pueden envolver tanto estática como método de instancia. La única diferencia es que para el delegado de creación con el método de instancia necesita instancia de la clase propietaria del método.

+0

Una vez creado el delegado p. delegado público void del (int i), el código permanece en una clase particular. Estaba pensando que el delegado necesita permanecer en la clase que tiene los métodos de interés o de lo contrario no podrá encontrarlos. Además, no entiendo por qué los delegados pueden crear un método estático? – BeraCim

+0

He actualizado la respuesta. –

0

¿Por qué quiere un delegado para esto? Suena demasiado complejo. Simplemente crearía un método en una nueva clase que podría instalar cuando necesitara llamarlo a usted. A esta clase se le puede dar alguna información de contexto para ayudarla a decidir. Luego implementaría la lógica en el nuevo método que decidiría si llamar al método actual o al método heredado.

Algo como esto:

public class CurrentOrLegacySelector<T> 
{ 

    public CurrentOrLegacySelector(some type that describe context) 
    { 
    // .. do something with the context. 
    // The context could be a boolean or something more fancy. 
    } 

    public List<T> GetNodes(int argument) 
    { 
    // Return the result of either current or 
    // legacy method based on context information 
    } 
} 

Esto le dará un contenedor limpio para los métodos que es fácil de leer y entender.

+0

Creo que la solución con delegado es más flexible: necesitará menos acciones para admitir el tercer método. Por otro lado, el tercer método puede no aparecer en el futuro :) –

+0

Un delegado no es más complicado, es mucho más simple. –

1

Vamos tanto ClassCurrent y MyClassLegacy implementar una interfaz INodeFetcher:

public interface INodeFetcher<T> { 
    List<T> GetNodes(int k); 
} 

Para ClassCurrent llamada al método GetAllRootNodes de la implementación del interfaz y de MyLegacyClass el método GetAllLeaveNodes.

+3

Si tiene un método en su interfaz, use un delegado en su lugar. Se ahorrará una * montaña * de código. Un delegado es como una interfaz de un solo método, además de una gran cantidad de compatibilidad de idiomas, lo que significa que no tiene que escribir clases con nombre separadas cada vez que quiera implementarlo. El tipo que está buscando aquí es 'Func >'. –

+0

@Daniel pero no podrá usar el delegado de forma polimórfica sin declararlo en una clase base común o interfaz y aún deberá configurar el delegado, que también requerirá una cantidad comparable de código. – Cornelius

+0

Ver mi respuesta. Configurar un delegado requiere mucho menos código que declarar e implementar una interfaz. Además, los delegados son polimórficos: la persona que llama simplemente obtiene un valor por el cual pueden realizar una llamada a través de un método, no están obligados a una implementación específica. Es exactamente como una interfaz de un solo método, pero con mucho menos código ceremonial requerido para usarlo. –

0

Como una variación del tema sugerido por Rune Grimstad creo que podría usar el patrón de estrategia (por ejemplo,
Introduction to the GOF Strategy Pattern in C#
).

Esto sería especialmente interesante en el caso donde no puede cambiar LegacyClass (y por lo tanto tal vez no pueda usar fácilmente el "enfoque de interfaz" sugerido por Cornelius) y si está usando la inyección de dependencia (DI; Dependency injection). DI (tal vez) le permitiría inyectar la implementación correcta (estrategia concreta) en el lugar correcto.

Estrategia:

public interface INodeFetcher<T> { 
    List<T> GetNodes(int k); 
} 

estrategias concretas:

public class CurrentSelector<T> : INodeFetcher<T> 
{ 
    public List<T> GetNodes(int argument) 
    { 
    // Return the result "current" method 
    } 
} 

public class LegacySelector<T> : INodeFetcher<T> 
{ 
    public List<T> GetNodes(int argument) 
    { 
    // Return the result "legacy" method 
    } 
} 

-> Inyectar/instancia de la estrategia concreta correcta.

Saludos

+0

Pero la interfaz tiene un método. Esta es simplemente una receta para mucha codificación manual innecesaria que se elimina mediante el uso de un delegado. –

+0

¿Dónde me estoy perdiendo algo? Por cierto, puede haber notado que no domino la programación de delegados; si pudieras ser amable :) – scherand

+0

¿viste mi respuesta? No estoy seguro de qué más agregarle ... si solo le preocupa cómo se ven sus requisitos hoy en día, entonces realmente no necesita organizar su código de ninguna manera en particular. Casi todas las funciones de lenguaje de alto nivel tratan de simplificar la evolución futura de su programa. Entonces, hoy puede que solo tenga dos implementaciones, pero ¿qué pasa en el futuro? –

Cuestiones relacionadas