1
votes

Stocker une action dans le dictionnaire

Ce que j'ai est un modèle de demande / réponse asynchrone, lorsque la réponse est reçue, je veux exécuter une action.

Un petit exemple de code de ce que j'aimerais accomplir:

class Program {
    static void Main(string[] args) {
        //Make a request and show 'hello world' when the response was received
        Connection.Request<MyResponse>("key", (MyResponse) => {
            Console.WriteLine(MyResponse.Value);
        });

        //set the response making the action write "hello world"
        Connection.SetResponse("key", new MyResponse("hello world"));
    }
}

public class Connection {
    static Dictionary<string, Action<BaseResponse>> _dicActions = new Dictionary<string, Action<BaseResponse>>();
    public static void Request<T>(string key, Action<T> action) where T : BaseResponse {
        _dicActions.Add(key, action);
    }

    public static void SetResponse(string key, BaseResponse pResponse) {
        _dicActions[key](pResponse);
    }
}

public class BaseResponse { }
public class MyResponse : BaseResponse {
    public string Value;
    public MyResponse(string pValue) {
        Value = pValue;
    }
}

Bien sûr, dans l'exemple ci-dessus, _dicActions.Add ne fonctionne pas. Comment puis-je faire fonctionner le code ci-dessous ?, pour une raison quelconque, je n'ai pas été en mesure de le comprendre.


1 commentaires

L'explication de l'erreur CS1661 que vous voyez est blogs .msdn.microsoft.com / csharpfaq / 2010/02/16 /… - vous pouvez définir Action sur l'instance de Action baseAction (parce que cette baseAction sera toujours appelée avec une instance de "Derived" qui est définitivement "Base", mais dans l'autre sens cela ne fonctionne pas car toutes les "Base" ne sont pas "Derived".


3 Réponses :


1
votes

Pourquoi n'utilisez-vous pas directement Action ?

public static void Main()
{
    //Make a request and show 'hello world' when the response was received
    Connection<MyResponse>.Request("key", (MyResponse) => {
        Console.WriteLine(MyResponse.Value);
    });

    //set the response making the action write "hello world"
    Connection<MyResponse>.SetResponse("key", new MyResponse("hello world"));
}

public class Connection<T> where T: BaseResponse
{
    static Dictionary<string, Action<T>> _dicActions = new Dictionary<string, Action<T>>();
    public static void Request(string key, Action<T> action)
    {
        _dicActions.Add(key, action);
    }

    public static void SetResponse(string key, T pResponse)
    {
        _dicActions[key](pResponse);
    }
}

Ou vous pouvez rendre la classe générique:

public static void Request<T>(string key, Action<BaseResponse> action) where T : BaseResponse


6 commentaires

Il convient également de noter que le paramètre générique est maintenant redondant et que l'OP devra également lancer Console.WriteLine (((MyResponse) MyResponse) .Value);


Parce que lorsque je ferais cela, je devrais convertir le paramètre de l'action dans MyResponse, je voudrais éviter de lancer un casting car cela évite les erreurs du développeur et est plus propre.


@Crazy semble rendre la classe Connection générique résout le problème comme le suggère ojlovecd


Cela résout effectivement le problème, l'a ajouté à mon application et fonctionne comme prévu


@Crazy créer beaucoup de types de frères avec des génériques peut ne pas être ce que vous voulez ... (comme vous ne pouvez plus avoir de collection d'instances "Connection") tant que cela fonctionne pour vous et que vous comprenez ce qui se passe réellement alors tout bon.


@AlexeiLevenkov, c'est en effet la raison pour laquelle je suis passé à la réponse donnée par Xiaosu. La réponse donnée par Enigmativity m'a obligé à refactoriser un peu mais c'est mieux car c'est un type sûr.



-1
votes

Si vous souhaitez simplement apporter une petite modification à votre code, vous pouvez convertir l'action comme suit:

_dicActions.Add(key, o => action((T)o));


4 commentaires

Bien sûr ... Le fait de déplacer les erreurs de compilation pour devenir des erreurs d'exécution est rarement une bonne idée.


Cela fonctionne avec des changements minimes, nécessite également moins de refactorisation de mon côté. Pourquoi aurais-je des erreurs d'exécution?, La diffusion en T ne peut pas vraiment faire que quelque chose se passe correctement?


@Crazy J'ai déjà ajouté des explications sur la question. Lisez-le attentivement. Il n'est pas possible de dire si cela s'applique à votre cas particulier, mais pour le code affiché jusqu'à présent, il peut facilement échouer à l'exécution: vous essayez d'appeler Action avec l'instance de MyOtherResponse : BaseResponse


C'est une si mauvaise idée - cela jette la sécurité de type.



1
votes

Pour faire cela correctement, vous devez vraiment vous assurer que le dictionnaire est de type sûr. C'est simple à faire. Changez simplement le dictionnaire de Dictionary > à Dictionary > , puis implémentez Connection code > comme ceci:

public class Connection
{
    static Dictionary<string, Dictionary<Type, Delegate>> _dicActions = new Dictionary<string, Dictionary<Type, Delegate>>();
    public static void Request<T>(string key, Action<T> action) where T : BaseResponse
    {
        if (!_dicActions.ContainsKey(key))
        {
            _dicActions.Add(key, new Dictionary<Type, Delegate>());
        }
        _dicActions[key].Add(typeof(T), action);
    }

    public static void SetResponse<T>(string key, T pResponse) where T : BaseResponse
    {
        ((Action<T>)_dicActions[key][typeof(T)])(pResponse);
    }
}

Cela fonctionne avec votre code existant comme une friandise et garantit que vous ne faites pas correspondre votre clé et votre T code> lors de l'appel de SetResponse.


2 commentaires

Joli. Dans le code de production, considérez que vous souhaitez ajouter une vérification nulle dans SetResponse , il y a également de fortes chances que NRE soit réellement correct dans ce cas, car je m'attendrais à ce que l'ensemble de gestionnaires corresponde à l'ensemble des réponses et le gestionnaire manquant est en fait un bogue plutôt qu'un comportement attendu.


@AlexeiLevenkov - Oui, je mettrais généralement un code de vérification nul ici. C'est juste un peu simplifié pour une compréhension plus facile.