12
votes

Comment obtenir une valeur via un paramètre OUT / REF à partir d'une méthode qui jette une exception?

Ce code émet "valeur de sortie".

class P
{
  public static void Main()
  {
    object[] args = new object[1];
    MethodInfo mi = typeof(P).GetMethod("Method");
    try
    {
      mi.Invoke(null, args);
    }
    catch
    {
    }
    Console.WriteLine(args[0]);
  }
  public static void Method(out string arg)
  {
    arg = "out value";
    throw new Exception();
  }
}


3 commentaires

Bonne question. Mais vous ne devriez pas compter sur la valeur si une méthode jette.


+1, bonne question, bien sûr, je devais l'essayer :) Je spéculerais que votre variable d'origine ne soit pas transmise à la fonction invoquée, elle reçoit une copie et que la copie se reflète dans l'original à la fin de la réussite. (lequel bien sûr ne se produit pas).


@slugster: votre spéculation est correcte. Je suppose qu'il n'y a pas de moyen de le faire avec une réflexion.


5 Réponses :


-1
votes

Le paramètre OUT n'est indéfini si la méthode jette une exception. Vous pouvez voir cela en ne l'initiant pas à NULL dans le premier exemple, puis le code ne compilera pas.

Donc, il est logique que la méthode d'invoque ne renvoie pas la valeur non définie si la méthode jette une exception.


13 commentaires

"Le paramètre OUT est indéfini si la méthode jette une exception." Pouvez-vous citer la spécification pour cela? "Vous pouvez voir cela en ne l'initiant pas à NULL dans le premier exemple, puis le code ne compilera pas." Ce n'est pas une preuve pour "indéfinie" du résultat. Cela ne prouve que ce n'est pas "définitivement attribué" selon les règles du compilateur C #. "Pas définitivement attribué" ne signifie pas non défini ou non attribué. C'est une chose fondamentalement différente.


>> "Le paramètre OUT est indéfini si la méthode jette une exception." Pourquoi donc? Oui, si vous supprimez l'initialisation, le code ne compilera pas. Mais pas à cause de la présence de montez dans la méthode (), mais à cause du bloc de capture vide dans la principale, c'est-à-dire que tous les chemins d'exécution initialisent une valeur de l'article avant l'utilisation réelle.


-1, le VAR OUT n'est pas non défini - exécutez le code et vous verrez.


@Mehrdad: non défini n'est pas la même chose que non assignée.


@IGOR: Oui, c'est le chemin d'exécution où la valeur n'est pas attribuée qui empêche le code de la compilation, mais l'essai ... la capture n'a rien à voir avec la manière dont le paramètre OUT est renvoyé. La méthode peut toujours jeter une exception indépendamment s'il y a un essai ... Catch, de sorte que le chemin d'exécution existent toujours.


@slugster: Je ne parle pas de non assigné, je parle d'indéfinie. Même si la valeur est attribuée avant que l'exception ne se produise, la valeur du paramètre OUT est toujours indéfinie. Il n'y a aucun moyen de dire à partir de la valeur du paramètre Out s'il a été attribué une valeur ou non.


Guffa: Je sais. C'est ma première question: "Pouvez-vous citer les spécifications qu'il est indéfini?" Depuis le fait qu'il ne compilait pas si vous ne l'initialisez pas, cela ne le prouve pas vraiment.


-1: c # Spec Section 5.1.6 dit "Un paramètre de sortie ne crée pas de nouvel emplacement de stockage. Au lieu de cela, un paramètre de sortie représente le même emplacement de stockage que la variable donnée comme argument dans l'élément de fonction ou l'invocation déléguée. Ainsi, le La valeur d'un paramètre de sortie est toujours la même que la variable sous-jacente. " Considérant que la cession modifie la valeur du paramètre de sortie, elle modifie également la valeur de la variable sous-jacente. Par conséquent, il n'est pas pertinent que la méthode jette ou non. Il n'est pas indéfini s'il est attribué dans la méthode avant la déclaration


Ce que vous voulez dire, cependant, (et cela est correct selon les spécifications) est que si la méthode jette, le paramètre de sortie ne deviendra pas définitivement attribué si ce n'est pas déjà définitivement attribué : "Chaque paramètre de sortie d'un élément de fonction ou d'une fonction anonyme doit être définitivement attribué (§5.3) avant la fonction de fonction ou la fonction anonyme retourne normalement ." [Mine Mine] Et c'est pourquoi la suppression de l'initialisation de la déclaration dans la première extrait permet de ne pas compiler. Ceci est complètement non pertinent pour la question, bien sûr. La question est une chose liée à la réflexion.


@Mehrdad: Oui, vous pouvez l'appeler "Pas définitivement attribué" si vous le souhaitez, c'est quand la valeur n'est pas définie.


@Guffa: Tout d'abord, cela ne s'applique pas à ce cas. La valeur est effectivement attribuée et définie ici. Deuxièmement, il y a une grande différence entre indéfini et non définitivement attribué. Une variable parfaitement définie peut être "non certainement assignée". Par exemple: int x = 10; int y; si (x == 10) {y = 100; } . Y n'est pas définitivement attribué ici mais il est logiquement attribué et la valeur est parfaitement définie. Je suppose que Eric Lippert a une entrée de blog sur ce sujet: blogs.msdn.com/ericlippert/archive/2009/10/12/...


@Mehrdad: Si vous dites que cela ne s'applique pas, vous ne comprenez pas la question ou ne comprenez pas ma réponse. Vous mélangez évidemment une "variable définie" avec "valeur définie". Ce n'est pas parce que le type de données de la variable est défini ne signifie pas que la valeur est définie.


@Guffa: Votre réponse stipule que la valeur du paramètre de sortie, si la callee lance est indéfinie. Ce que je comprends de cette déclaration est que le compilateur est autorisé à produire tout ce qu'il aime pour le premier extrait de code que l'OP a affiché et est toujours conforme à la spécification. Ceci n'est clairement pas vrai.



1
votes

L'exception a contourné le code dans MethodInfo.Invoke () qui copie la valeur [OUT] du cadre de la pile dans la matrice d'objet. La valeur sur le cadre de pile qui invoque () créée se comporte comme si elle fait dans votre 1ère extrait. Mais c'est là que les similitudes se terminent.


0 commentaires

1
votes

Le seul moyen est de surcharger votre méthode de manière à rendre compte de la possibilité d'une exception, puis de passer un dans "juste au cas". Ce qui suit produit ce que je pense que vous recherchez. Le problème que je comprends, c'est que la réflexion n'effectue pas de manipulation directe des adresses transmises par référence. Les adresses ne sont pas affectées tant que le point final est atteint sans exception. Éventuellement un schéma de protection de la mémoire ou de sécurité de la mémoire de MS.

class P
    {
        public static void Main()
        {
            object[] args = { "1", new Exception()};
            MethodInfo mi = typeof(P).GetMethod("Method");
            try
            {
                mi.Invoke(null, args);
            }
            catch
            {
            }
            Console.WriteLine(args[0].ToString());
            Console.WriteLine(args[1].ToString());
        }
        public static void Method(ref string arg, ref Exception ex)
        {
            try
            {
                arg = "out value";
                throw new Exception();
            }
            catch (Exception exc)
            {
                ex = exc;
            }
        }
}


2 commentaires

Je ne pense pas que cela résout le problème fondamental. Si nous avions ce type de contrôle pour la méthode invoquée, nous n'utiliserions pas de réflexion. Le but est que ce type de méthode pourrait exister ailleurs et nous devrions peut-être l'appeler à l'aide de la réflexion. Comment ferions-nous cela? Je ne pense pas que ce soit possible.


Je conviens que ce n'est pas possible de la manière décrite. J'ai eu le sens du poste qu'il avait le contrôle de la méthode réfléchie, alors je pensais proposer une œuvre de travail.



0
votes

Je proposerais de changer la méthode pour renvoyer l'objet de résultat au lieu du paramètre OUT. L'objet de résultat peut contenir une exception ainsi que la valeur de votre arg.


1 commentaires

Comme je l'ai noté dans le commentaire de la réponse de @ Joel, la question est fondamentale. Et si nous n'avions aucun contrôle sur la méthode cible. Une méthode arbitraire est là et nous devons l'appeler à l'aide de la réflexion. Comment ferions-nous cela?



0
votes

Si le problème est, comment attrapez-vous qu'une exception s'est produite et que vous travaillez avec une application Windows Forms, avez-vous essayé de regarder l'événement d'exception de fil et de la combiner avec le SETUnhandleXceptionMode ()?

Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)        
{            
    HandleException(e.Exception);        
}


1 commentaires

Où est la valeur du paramètre de sortie? Je pense que vous avez mal compris la question. Il ne s'agit pas d'attraper l'exception. Le problème est, lors de l'utilisation de la réflexion, si la callee jette, la valeur du paramètre de sortie n'est pas attribuée. Cela ne résout pas le problème.