views:

236

answers:

5

this code outputs "out value".

class P
{
  public static void Main()
  {
    string arg = null;
    try
    {
      Method(out arg);
    }
    catch
    {
    }
    Console.WriteLine(arg);
  }
  public static void Method(out string arg)
  {
    arg = "out value";
    throw new Exception();
  }
}

but this one doesn't.

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

how can I get both "out value" and an exception when using reflection?

A: 

The out parameter is undefined if the method throws an exception. You can see this by not initialising it to null in the first example, then the code won't compile.

So, it makes sense for the Invoke method not to return the undefined value if the method throws an exception.

Guffa
"The out parameter is undefined if the method throws an exception." Can you cite the spec for this? "You can see this by not initialising it to null in the first example, then the code won't compile." This is not a proof for "undefinedness" of the result. It only proves that it's not "definitely assigned" according to the C# compiler rules. "Not definitely assigned" doesn't mean undefined or unassigned. It's a fundamentally different thing.
Mehrdad Afshari
>> "The out parameter is undefined if the method throws an exception." Why is that?Yes, if you remove initialisation then the code won't compile. But not because of the presence of throw in Method(), but because of the empty catch block in Main, i.e. not all execution paths initialise a value of arg before actual usage.
Igor Korkhov
-1, the out var is not undefined - run the code and you'll see.
slugster
@Mehrdad: Undefined is not the same thing as unassigned.
Guffa
@Igor: Yes, it's the execution path where the value is unassigned that keeps the code from compiling, but the try...catch has nothing to do with how the out parameter is returned. The method can always throw an exception regardless if there is a try...catch, so that execution path always exist.
Guffa
@slugster: I'm not talking about unassigned, I'm talking about undefined. Even if the value happens to be assigned before the exception occurs, the value of the out parameter is still undefined. There is no way of telling from the value of the out parameter if it has been assigned a value or not.
Guffa
Guffa: I know. That's my first question: "Can you cite the spec that it's undefined?" Since the fact that it won't compile if you don't initialize it doesn't really prove it.
Mehrdad Afshari
-1: C# Spec Section 5.1.6 says "An output parameter does not create a new storage location. Instead, an output parameter represents the same storage location as the variable given as the argument in the function member or delegate invocation. Thus, the value of an output parameter is always the same as the underlying variable." Considering the fact that assignment changes the value of output parameter, it also changes the value of the underlying variable. Therefore, it's irrelevant that the method throws or not. It is not undefined if it's assigned in the method before the `throw` statement
Mehrdad Afshari
What you mean, though, (and this is correct according to spec) is that if the method throws, the output parameter will not become *definitely assigned* if it's not already *definitely assigned* : "Every output parameter of a function member or anonymous function must be definitely assigned (§5.3) before the function member or anonymous function **returns normally**." *[emphasis mine]* and that's why removing the initialization from the declaration in the first snippet makes it fail to compile. This is completely irrelevant to the question, of course. The question is a reflection-related thing.
Mehrdad Afshari
@Mehrdad: Yes, you can call it "not definitely assigned" if you like, that's when the value is undefined.
Guffa
@Guffa: First, it doesn't apply to this case. The value is actually assigned and defined here. Second, there's a big difference between undefined and not definitely assigned. A perfectly defined variable can be "not definitely assigned". For instance: `int x = 10; int y; if (x == 10) { y = 100; }`. Y is not definitely assigned here but it's logically assigned and the value is perfectly defined. I guess Eric Lippert has a blog entry on this topic: http://blogs.msdn.com/ericlippert/archive/2009/10/12/absence-of-evidence-is-not-evidence-of-absence.aspx
Mehrdad Afshari
@Mehrdad: If you say that it doesn't apply, then you either don't understand the question or don't understand my answer. You are obviously mixing up "defined variable" with "defined value". Just because the data type of the variable is defined doesn't mean that it's value is defined.
Guffa
@Guffa: Your answer states that the value of the output parameter, if the callee throws is undefined. What I understand from this statement is that the compiler is allowed to output *anything it likes* for the first code snippet that the OP posted and still conform to the spec. This is clearly not true.
Mehrdad Afshari
+1  A: 

The exception bypassed the code in MethodInfo.Invoke() that copies the [out] value from the stack frame back into the object array. The value on the stack frame that Invoke() created behaves just like it does in your 1st snippet. But that's where the similarities end.

Hans Passant
+1  A: 

The only way is to overload your method in a manner that accounts for the possibility of an exception and then pass one in "just in case". The following produces what I think you're looking for. The problem as I understand it is that reflection does not perform direct manipulation of the addresses passed in by reference. The addresses are not affected until the method end point is reached without exception. Possibly a memory protection or memory security scheme from 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;
            }
        }
}
Joel Etherton
I don't think this solves the fundamental problem though. If we had that kind of control for the invoked method, we wouldn't use reflection. The point is, that kind of method might exist elsewhere and we might need to call it using reflection. How would we do that? I don't think that's possible.
Mehrdad Afshari
I agree that it's not possible in the manner described. I got the sense from the post that he did have control of the reflected method though, so I thought I would propose a work-around.
Joel Etherton
A: 

I would propose to change the method to return Result object instead of out parameter. Result object can contain exception and also the value of your arg.

kosto
As I noted in the comment in @Joel's answer, the question is a fundamental one. What if we didn't have any control on the target method. An arbitrary method is there and we need to call it using reflection. How would we do that?
Mehrdad Afshari
A: 

If the issue is, how do you catch that an exception has occured and you're working with a Windows Forms application, have you tried looking at the Thread Exception Event and combining it with the SetUnhandledExceptionMode()?

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);        
}
Plip
Where's the value of the output parameter? I think you misunderstood the question. It's not about catching the exception. The problem is, when using reflection, if the callee throws, the value of the output parameter is not assigned. This does not solve the problem.
Mehrdad Afshari