简体   繁体   中英

Dynamic cast vs reflection invoke: which poison to pick?

I'm facing a little dilemma.

I need to repetitively call a given method on many instances of a type, that may or may not exist. Because the method's result will never change for any given instance, I'm caching its return value for future use so as to reduce the overhead and return early on subsequent calls, but still the part that actually does the work needs to call that method-that-may-not-exist :

var method = Context.Parent.GetType().GetMethod("typeHint");
if (method == null)
{
    token = null;
    _hasTypeHint = false;
    return false;
}

var hint = ((dynamic)Context.Parent).typeHint() as VBAParser.TypeHintContext;
token = hint == null ? null : hint.GetText();
return hint != null;

Catching/handling a RuntimeBinderException was a major performance bottleneck, so I decided to reflect on the type in question to go and discover whether the method exists or not before calling it.

The specific type of Context.Parent is unknown at compile-time, and the possible runtime types don't share a common interface that would have the typeHint() method I'm looking for: I need to either invoke the reflected member, or cast to dynamic and invoke it directly.

My commit message goes like this:

removed the possibility for RuntimeBinderException by reflecting on the context type to locate the method to use. Kept (dynamic) cast because deemed less expensive than reflection invoke .. could be wrong though.

Which one incurs the least overhead, and more importantly, why ?

Well, I was curious so I wrote a little program to test:

        var sw = new Stopwatch();
        int loopLimit = 10000000;
        object hint1;
        sw.Start();
        for( var i = 0; i < loopLimit; i++ )
            hint1 = ( ( dynamic )theObject ).ToString();
        sw.Stop();
        Console.WriteLine( "dynamic time: {0}", sw.ElapsedMilliseconds );
        sw.Restart();
        for( var i = 0; i < loopLimit; i++ )
            hint1 = method.Invoke( theObject, null );
        sw.Stop();
        Console.WriteLine( "invoke time: {0}", sw.ElapsedMilliseconds );
        Console.ReadLine();

Playing with different values of loopLimit you'll see that for a relatively low number of calls Invoke is faster. As you increase loopLimit the dynamic call catches up and then it becomes much faster. Why? Because a dynamic call caches the Expression tree required to do the operation. And in this case, there is only one Expression tree created as I didn't change theObject (at a call site there will be only one Expression tree per object type).

So, long story short, if you are only calling a few thousand times or if you have a large variety of object types to test and call, then the Expression tree creation is going to kill your performance and Invoke will be better. If objects can be one of a few types and your method is called millions of times, then you may be better off with dynamic calls.

Play with the numbers for your particular situation. My guess is that you'll pick Invoke .

Not sure if this would work for you....

public async Task<tokenclass> Process(Button Context){
            var x = await Task.Run(() =>
            {
                tokenclass tc = new tokenclass();
                var method = Context.Parent.GetType().GetMethod("typeHint");
                if (method == null)
                {
                    tc.token = null;
                    tc.hasTypeHing = false;
                    tc.result = false;                   
                }
                return tc;
            });
            return x;


   public class tokenclass
    {
        public string token { get; set; }
        public bool hasTypeHing { get; set; }
        public bool result { get; set; }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM