2010-03-17 6 views
6

J'ai actuellement 2 méthodes concrètes dans 2 classes abstraites. Une classe contient la méthode actuelle, tandis que l'autre contient la méthode héritée. Par exemple.C# delegate et abstract class

// 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 
    } 
} 

Je souhaite que la méthode correspondante s'exécute dans leurs scénarios relatifs dans l'application. Je prévois d'écrire un délégué pour gérer cela. L'idée est que je peux simplement appeler le délégué et y écrire une logique pour gérer la méthode à appeler en fonction de la classe/projet à laquelle il est appelé (au moins c'est ce que je pense que les délégués sont et comment ils sont utilisés).

Cependant, j'ai quelques questions sur ce sujet (après quelques googler):

1) Est-il possible d'avoir un délégué qui connaît les 2 méthodes (ou plus) qui résident dans différentes classes? 2) Est-il possible de créer un délégué qui génère des classes abstraites (comme dans le code ci-dessus)? (Ma conjecture est un non, puisque les délégués créent une implémentation concrète des classes passées) 3) J'ai essayé d'écrire un délégué pour le code ci-dessus. Mais je suis mis au défi sur le plan technique:

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

j'ai eu l'erreur suivante:

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

je pourrais avoir mal compris quelque chose ... mais si je dois déclarer manuellement un délégué à la classe d'appel, ET pour passer la fonction manuellement comme ci-dessus, alors je commence à me demander si le délégué est un bon moyen de gérer mon problème.

Merci.

Répondre

7

La façon dont vous essayez d'utiliser des délégués (les construire avec new, déclarant un type délégué nommé) suggère que vous utilisez C# 1. Si vous êtes en train de l'aide C# 3, il est beaucoup plus facile que cela.

Tout d'abord, votre type de délégué:

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

existe déjà. C'est juste:

Func<int, List<BaseNode>> 

Donc vous n'avez pas besoin de déclarer votre propre version. Deuxièmement, vous devriez penser à un délégué comme étant une interface avec une seule méthode, et vous pouvez le "mettre en œuvre" à la volée, sans avoir à écrire une classe nommée. Écrivez simplement un lambda ou attribuez un nom de méthode directement.

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 est de la forme (arguments) => { body; }. Les arguments sont séparés par des virgules. S'il n'y en a qu'un, vous pouvez omettre les parenthèses. Si elle ne prend aucun paramètre, mettez une paire de parenthèses vides: (). Si le corps n'a qu'un seul énoncé, vous pouvez omettre les accolades. Si ce n'est qu'une seule expression, vous pouvez omettre les accolades et le mot-clé return. Dans le corps, vous pouvez faire référence à pratiquement toutes les variables et méthodes de la portée englobante (à l'exception des paramètres ref/out de la méthode englobante).

Il n'est presque jamais nécessaire d'utiliser new pour créer une instance de délégué.Et rarement un besoin de déclarer les types de délégués personnalisés. Utilisez Func pour les délégués qui renvoient une valeur et Action pour les délégués qui renvoient void. Chaque fois que la chose que vous devez faire circuler est comme un objet avec une méthode (une interface ou une classe), alors utilisez un délégué à la place, et vous serez en mesure d'éviter beaucoup de désordre.

En particulier, évitez de définir des interfaces avec une méthode. Il va simplement dire qu'au lieu d'être capable d'écrire un lambda pour mettre en œuvre cette méthode, vous devez déclarer une classe nommée distincte pour chaque application différente, avec le motif:

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 fait effectivement tout ce qui pour vous, en éliminant ces motifs mécaniques de votre code. Vous dites que:

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

Il a également l'avantage que vous pouvez mettre à jour les variables dans la portée englobante, parce que vous pouvez vous référer directement à eux (au lieu de travailler avec des valeurs copiées dans les champs d'une classe). Parfois, cela peut être un inconvénient, si vous ne voulez pas modifier les valeurs.

+0

Si vous souhaitez que le délégué mappe à une méthode spécifique s'il est de type 'ClassCurrent' (le' GetAllRootNodes') et une autre méthode s'il est de type 'MyClassLegacy' (le' GetAllLeaveNodes') alors vous devez connaître le type de l'instance. Alors que si elle partage une interface commune, il n'est pas nécessaire de connaître le type juste pour implémenter l'interface donnée. Le code pour configurer le délégué pour chaque type contiendra soit un if pour vérifier le type ou une méthode qui prend le type spécifique comme argument pour chaque type autorisé. – Cornelius

+0

@Cornelius - que pensez-vous qu'un délégué est? Il semble que vous pensiez que c'est la même chose qu'un pointeur de fonction dans C. –

+2

Les délégués sont plus puissants que les pointeurs de fonction car ils sont des "méthodes de première classe" et ont une fermeture, mais cela ne vous permet que de les passer polymorphiquement. éliminer la complexité de la mise en place comme dans le commentaire ci-dessus. – Cornelius

1

Vous pouvez avoir un délégué initialisé avec des références à différentes méthodes en fonction de certaines conditions. En ce qui concerne vos questions:
1) Je ne suis pas sûr de ce que vous entendez par "sait". Vous pouvez passer n'importe quelle méthode au délégué, donc si vous pouvez écrire une méthode qui "sait" à propos d'autres méthodes que vous pouvez faire un délégué similaire.
2) Encore une fois, les délégués peuvent être créés à partir de n'importe quelle méthode qui peut être exécutée. Par exemple, si vous avez une variable locale initialisée de type ClassCurrent<T>, vous pouvez créer un délégué pour toute méthode d'instance de type ClassCurrent<T>.
3) Le délégué ne peut appeler que la méthode qui peut effectivement être appelée. Je veux dire que vous ne pouvez pas appeler ClassCurrent.GetAllRootNodes car GetAllRootNodes n'est pas une méthode statique, vous avez donc besoin d'une instance du ClassCurrent pour l'appeler. Le délégué peut rester dans n'importe quelle classe ayant accès au ClassCurrent et au MyClassLegacy.

Par exemple, vous pouvez créer smth comme:

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); 
    } 
} 

Les délégués peuvent envelopper à la fois statique et méthode par exemple. La seule différence est que pour la délégation de création avec la méthode d'instance, vous avez besoin de l'instance de la classe qui possède la méthode.

+0

Une fois le délégué créé, par ex. délégué public void del (int i), le code reste dans une classe particulière. Je pensais que le délégué doit rester dans la classe qui a les méthodes d'intérêt, sinon il ne pourra pas les trouver. En outre, je ne comprends pas pourquoi les délégués peuvent créer une méthode statique? – BeraCim

+0

J'ai mis à jour la réponse. –

0

Pourquoi voudriez-vous un délégué pour cela? Cela semble trop complexe. Je voudrais juste créer une méthode dans une nouvelle classe que vous pourriez instansiate quand vous avez besoin de vous appeler la méthode. Cette classe pourrait recevoir des informations contextuelles pour l'aider à décider. Ensuite, j'implémenterais la logique dans la nouvelle méthode qui déciderait d'appeler la méthode actuelle ou la méthode héritée.

Quelque chose comme ceci:

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 
    } 
} 

Cela vous donne un emballage propre pour les méthodes qui est facile à lire et à comprendre.

+0

Je pense que cette solution avec délégué est plus flexible - vous aurez besoin de moins d'actions pour prendre en charge la troisième méthode. D'un autre côté la troisième méthode peut ne pas apparaître dans le futur :) –

+0

Un délégué n'est pas plus compliqué, c'est beaucoup plus simple. –

1

Laissez-les ClassCurrent et MyClassLegacy implémenter une interface INodeFetcher:

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

Pour ClassCurrent appel à la méthode GetAllRootNodes de la mise en œuvre de l'interface et pour MyLegacyClass la méthode GetAllLeaveNodes.

+3

Si vous avez une méthode dans votre interface, utilisez plutôt un délégué. Vous allez vous épargner une * montagne * de code. Un délégué est comme une interface à une méthode, plus une prise en charge linguistique importante, ce qui signifie que vous n'avez pas besoin d'écrire des classes nommées distinctes chaque fois que vous voulez l'implémenter. Le type que vous cherchez ici est 'Func >'. –

+0

@Daniel mais vous ne pourrez pas utiliser le délégué de façon polymorphique sans le déclarer sur une classe de base ou une interface commune et vous devrez toujours configurer le délégué qui nécessitera également une quantité de code comparable. – Cornelius

+0

Voir ma réponse. La configuration d'un délégué nécessite beaucoup moins de code que la déclaration et l'implémentation d'une interface. En outre, les délégués sont en effet polymorphes: l'appelant obtient simplement une valeur qu'il peut passer par un appel de méthode, ils ne sont pas liés à une implémentation spécifique. C'est exactement comme une interface à une méthode, mais avec beaucoup moins de code cérémoniel requis pour l'utiliser. –

0

Comme une variation du thème suggéré par Rune Grimstad, je pense que vous pourriez utiliser le modèle de stratégie (par exemple
Introduction to the GOF Strategy Pattern in C#
). Ceci serait particulièrement intéressant dans le cas où vous ne pouvez pas changer le LegacyClass (et donc ne pouvez pas facilement utiliser l'approche d'interface suggérée par Cornelius) et si vous utilisez l'injection de dépendance (DI; Dependency injection). DI (peut-être) vous permet d'injecter la bonne implémentation (stratégie concrète) au bon endroit.

Stratégie:

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

stratégies concrètes:

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 
    } 
} 

-> Injecter/instancier la stratégie concrète correcte.

Cordialement

+0

Mais l'interface a une méthode. C'est simplement une recette pour beaucoup de main-codage inutile qui est éliminé en utilisant un délégué. –

+0

Où est-ce que je manque quelque chose? En passant, vous avez peut-être remarqué que je ne parle pas couramment la programmation des délégués. si vous pouviez s'il vous plaît être doux :) – scherand

+0

avez-vous vu ma réponse? Je ne sais pas quoi ajouter d'autre ... si vous êtes seulement préoccupé par la façon dont vos exigences se présentent aujourd'hui, alors vous n'avez pas vraiment besoin d'organiser votre code d'une manière particulière. Presque toutes les fonctionnalités linguistiques de haut niveau ont pour but de simplifier l'évolution future de votre programme. Donc, aujourd'hui, vous ne pouvez avoir que deux implémentations, mais qu'en est-il dans le futur? –