Retrieve MethodInfo of the method that is before t

2019-05-10 04:25发布

问题:

I've created wrapper for logging library inside of which I want to log parameters of the outer method that thrown exception:

public void MethodA(string param1, int param2)
{
  try
  {
    throw new Exception();
  }
  catch(Exception ex)
  {
    Logger.Error(ex);
  }
}

...

public static void Error(Exception ex)
{
...
}

As you can see I would like to get MethodA informations on the Error method level. I would be more than happy just to use:

ParameterInfo[] pi = new StackFrame(1).GetMethod().GetParameters();

because it has everything, but I just read multiple SO posts about poor performance. Seems to me that maybe it's not the best way then (on the other hand, we're talking about exception handling - at this point of time it's already not too important). However, I cannot find traditional approach using Reflection to solve this. Perhaps someone can enlighten me?

Or maybe you would still go with StackFrame solution ? It's asp.net application with rather small demands for ultra performance.

Edit1: Maybe I was not so sure what I want to write. I AM going with StackFrame solution even if it can take up to 20 ms to get a frame but I am really curious how to get similiar result with reflection.

回答1:

You might find this utility method of mine useful, since it also solves another problem which you are likely to come across: things are a bit weird when you want to get the current stack frame from within a constructor.

using Collections = MikeNakis.Abstract.Collections;
using Diagnostics = System.Diagnostics;
using Generic = System.Collections.Generic;
using Reflection = System.Reflection;
[...]
    ///<summary>Collect stack frames</summary>
    ///<param name="frames_to_skip">Number of frames to skip (usually 1)</param>
    ///<param name="type">When invoking from within a constructor, specify the type of the containing class so as to
    ///skip any other constructors.</param>
    public static Generic.IList<Diagnostics.StackFrame> CollectStackframes( int frames_to_skip, System.Type type = null )
    {
        var frames = new Diagnostics.StackTrace( frames_to_skip + 1, fNeedFileInfo:true ).GetFrames();
        int i = 0;
        if( type != null )
        {
            for( ;  i < frames.Length;  i++ )
            {
                Reflection.MethodBase method = frames[i].GetMethod();
                if( !method.IsConstructor )
                    break;
                //Does not work: if( method.DeclaringType == type )
                //Does not work: if( Equals( method.DeclaringType, type ) )
                //Does not work: if( Equals( method.DeclaringType.TypeHandle, type.TypeHandle ) )
                if( Equals( method.DeclaringType.GUID, type.GUID ) )
                {
                    i++;
                    break;
                }
            }
        }
        var list_of_frame = new Generic.List<Diagnostics.StackFrame>( frames.Length - i );
        for( ;  i < frames.Length;  i++ )
            list_of_frame.Add( frames[i] );
        return Collections.Util.NewListReadonly( list_of_frame );
    }

(Note: Collections.Util.NewListReadonly() is a static utility method of mine which creates a read-only list and returns its IList<T> interface.)

I do not know about the performance of new Diagnostics.StackTrace(): I would guess that it is as slow as using reflection, (it probably counts as reflection,) but probably a bit faster than throwing an exception.

I will disappoint you about the parameters part though: I have found no way to obtain the contents of parameters to methods at runtime. So, all that you can hope to get is the types and names of the parameters, but not their values. If you find a way, please let me know.



回答2:

There are a few different aspects to this question:

I want to log parameters of the outer method

You can use an interception-based technique built off your favorite IoC container, but I've honestly found the best approach is usually just to pass the parameters into the exception information:

throw new Exception("Something went wrong..." + new{param1, param2});

Retrieve MethodInfo of the method that is before the active one on the calling stack?

From what I can gather, you're only ever planning to use this to log an error when an exception is thrown. In that case, I'd just stick with your StackFrame-based solution for the time being, since it's unlikely to occur very often. When C# 5 comes out, you'll be able to use the [CallerMemberName] attribute:

public static void Error(Exception ex, [CallerMemberName] string caller = "") {
    ...
}

A word to the wise

It's good practice to limit the number of places where you catch exceptions like this. Generally it's best just to let the exception pass itself up the chain until the last possible moment, where you can gracefully tell the user something unexpected happened and log the error at that point. This gives you a much more informative stacktrace on the Exception itself. If you want to pass additional information along at some point along the way, just wrap the exception in an inner exception and throw that:

catch(Exception ex)
{
   throw new Exception("Something went wrong..." + new{param1, param2}, ex);
}


回答3:

Or maybe you would still go with StackFrame solution ? It's asp.net application with rather small demands for ultra performance.

I would go with this approach so long as you use exceptions only in error conditions, not as a "return value" of a method. (See Exceptions vs. Return Values). If you don't throw a lot of exceptions -- and most applications don't -- then you'll be fine.

Our group writes trading software that we heavily monitor for performance. So long as the thrown exceptions don't occur in a critical path you're fine. Usually, if an unhandled exception is thrown then performance isn't relevant: the user isn't waiting for a response. Or that response won't arrive because of the exception.

Note that you may want to do an index check before using a hard-coded new StackFrame(1).



回答4:

None of these. I would go with an interception solution using something like Castle Windsor. Then it's very easy to get the parameters to the method that was intercepted.