Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

467
Views
How to speed up dynamic C# (.NET) execution from C++?

I've got an advanced question and it has taken me many hours so far to try to come up with a solution that doesn't use reflection.

The scenario is that we have a C++ application that runs our own interpreted script language and that scripting language can interface with C# (.NET), for example to create Windows Forms dialogs. We achieve this by using the ICorRuntimeHost, a C++/CLI helper library and ultimately reflection to call methods, properties, etc.

I've taken a step back and am trying to see how I can most efficiently execute code in C# without actually writing the method calls directly. I'm using BenchmarkDotNet to benchmark the code. Also I'm intending to only use C++/CLI without ICorRuntimeHost, which would mean that the C# code below would need to be written in C++/CLI ultimately.

The benchmark code is as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using BenchmarkDotNet.Attributes;

namespace DotNetExecutorTest
{
    public class BenchmarksIndexOf
    {
        private readonly int NumberOfCalls = 1000;

        [Benchmark]
        public long Reflection()
        {
            long sum = 0;
            for (int i = 0; i < NumberOfCalls; i++)
            {
                sum += (int)(typeof(string).InvokeMember("IndexOf", System.Reflection.BindingFlags.InvokeMethod, null, "Hello", new object[] { 'H' }));
            }
            return sum;
        }

        [Benchmark]
        public long TypedOpenDelegate()
        {
            long sum = 0;
            var methodInfo = typeof(string).GetMethod("IndexOf", new Type[] { typeof(char) });
            Func<string, char, int> func = (Func<string, char, int>)Delegate.CreateDelegate(typeof(Func<string, char, int>), methodInfo);
            for (int i = 0; i < NumberOfCalls; i++)
            {
                sum += func("Hello", 'H');
            }
            return sum;
        }

        [Benchmark]
        public long NativeCSharp()
        {
            long sum = 0;
            for (int i = 0; i < NumberOfCalls; i++)
            {
                sum += "Hello".IndexOf('H');
            }
            return sum;
        }

        [Benchmark]
        public long FastExecutorV1()
        {
            long sum = 0;
            for (int i = 0; i < NumberOfCalls; i++)
            {
                sum += (int)(DotNetExecutorFastV1.ExecuteMethod(typeof(string), "IndexOf", false, "Hello", new Type[] { typeof(char) }, new object[] { 'H' }));
            }
            return sum;
        }

        [Benchmark]
        public long FastExecutorV2()
        {
            long sum = 0;
            for (int i = 0; i < NumberOfCalls; i++)
            {
                sum += (int)(DotNetExecutorFastV2.ExecuteMethod(typeof(string), "IndexOf", false, "Hello", new Type[] { typeof(char) }, new object[] { 'H' }));
            }
            return sum;
        }

        [Benchmark]
        public long SlowExecutor()
        {
            long sum = 0;
            for (int i = 0; i < NumberOfCalls; i++)
            {
                sum += (int)(DotNetExecutorSlow.ExecuteMethod(typeof(string), "IndexOf", false, "Hello", new Type[] { typeof(char) }, new object[] { 'H' }));
            }
            return sum;
        }

        [Benchmark]
        public long MethodInfoExecutor()
        {
            long sum = 0;
            for (int i = 0; i < NumberOfCalls; i++)
            {
                sum += (int)(DotNetExecutorMethodInfo.ExecuteMethod(typeof(string), "IndexOf", false, "Hello", new Type[] { typeof(char) }, new object[] { 'H' }));
            }
            return sum;
        }
    }
}

And the results are:

Method Mean Error StdDev Median
Reflection 1,106.853 us 53.9937 us 159.2016 us 1,000.888 us
TypedOpenDelegate 6.937 us 0.0802 us 0.0626 us 6.942 us
NativeCSharp 3.328 us 0.0430 us 0.0381 us 3.312 us
FastExecutorV1 208.947 us 1.1720 us 1.0390 us 208.974 us
FastExecutorV2 203.321 us 3.9627 us 4.2400 us 202.109 us
SlowExecutor 1,184.866 us 7.4664 us 6.2348 us 1,182.970 us
MethodInfoExecutor 401.875 us 2.3886 us 2.1175 us 402.048 us

Quick explanation of the methods is as follows:

  • Reflection: Plain simple reflection, it is what we are currently using.
  • TypedOpenDelegate: The most promising solution at first glance, however because all our .NET members are invoked dynamically we can not use the static typing of Func<string, char, int> and that seems to be exactly where the performance is gained.
  • NativeCSharp: Just simple C#.
  • FastExecutorV1: Custom executing based on expression-trees that store Func<object, object[], object> instances, based on open delegates, will be explained later.
  • FastExecutorV1V Custom executing based on expression-trees that store Func<object, object[], object> instances, based on method calls, will be explained later.
  • SlowExecutor: Storing open delegates of the MethodInfo objects and then calling DynamicInvoke on them, the performance is even worse than just using reflection.
  • MethodInfoExecutor: Promising performance by simply calling MethodInfo.Invoke, but not the best performance.

So first I can explain the MethodInfoExecutor, it is actually quite simple:

public class DotNetExecutorMethodInfo
{
    private static readonly Dictionary<DotNetKey, MethodInfo> methodInfos = new Dictionary<DotNetKey, MethodInfo>();

    public static void ResetDictionary()
    {
        methodInfos.Clear();
    }

    public static object ExecuteMethod(Type type, string name, bool isStatic, object instance, Type[] parameterTypes, object[] parameters)
    {
        var dotNetKey = new DotNetKey
        {
            type = type,
            dotNetType = DotNetType.Method,
            name = name,
            isStatic = isStatic,
            parameterTypes = parameterTypes
        };
        if (!methodInfos.TryGetValue(dotNetKey, out var methodInfo))
        {
            methodInfo = type.GetMethod(name, BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance), null, parameterTypes, null);
            methodInfos[dotNetKey] = methodInfo;
        }
        return methodInfo.Invoke(isStatic ? null : instance, parameters);
    }
}

For completeness the DotNetKey source:

public struct DotNetKey : IEquatable<DotNetKey>
{
    public Type type;
    public DotNetType dotNetType;
    public bool isStatic;
    public string name;
    public Type[] parameterTypes;

    public bool Equals(DotNetKey other)
    {
        if (type != other.type)
        {
            return false;
        }
        if (dotNetType != other.dotNetType)
        {
            return false;
        }
        if (isStatic != other.isStatic)
        {
            return false;
        }
        if (name != other.name)
        {
            return false;
        }
        if (!Enumerable.SequenceEqual(parameterTypes, other.parameterTypes))
        {
            return false;
        }
        return true;
    }

    public override int GetHashCode()
    {
        var hash = 17 * (type, dotNetType, isStatic, name).GetHashCode();
        foreach (var type in parameterTypes)
        {
            hash = hash * 23 + type.GetHashCode();
        }
        return hash;
    }
}

And DotNetType:

public enum DotNetType
{
    Method
}

And then we have our complex solution which is as follows, it even has two versions with the same performance.

Version 1, the original version with open delegates:

public class DotNetExecutorFastV1
{
    private static readonly Dictionary<DotNetKey, Func<object, object[], object>> funcs = new Dictionary<DotNetKey, Func<object, object[], object>>();

    public static object ExecuteMethod(Type type, string name, bool isStatic, object instance, Type[] parameterTypes, object[] parameters)
    {
        var func = FindFunc(type, DotNetType.Method, name, isStatic, parameterTypes);
        return func(instance, parameters);
    }

    private static Func<object, object[], object> FindFunc(Type type, DotNetType dotNetType, string name, bool isStatic, Type[] parameterTypes)
    {
        var dotNetKey = new DotNetKey
        {
            type = type,
            dotNetType = dotNetType,
            name = name,
            isStatic = isStatic,
            parameterTypes = parameterTypes
        };
        if (funcs.TryGetValue(dotNetKey, out var func))
        {
            return func;
        }
        else
        {
            var newFunc = CreateMethodFunc(type, name, isStatic, parameterTypes);
            funcs.Add(dotNetKey, newFunc);
            return newFunc;
        }
    }

    private static Func<object, object[], object> CreateMethodFunc(Type type, string name, bool isStatic, Type[] parameterTypes)
    {
        var methodInfo = type.GetMethod(name, BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance), null, parameterTypes, null);
        var openDelegate = Delegate.CreateDelegate(OpenDelegateType(type, isStatic, methodInfo, parameterTypes), methodInfo);

        var instance = Expression.Parameter(typeof(object), "instance");
        var parameterArray = Expression.Parameter(typeof(object[]), "parameterArray");
        var parameterExpressions = parameterTypes.Select((parameterType, index) => Expression.Convert(Expression.ArrayAccess(parameterArray, Expression.Constant(index)), parameterType));
        var parameters = new List<Expression>();
        if (isStatic)
        {
            parameters.AddRange(parameterExpressions);
        }
        else
        {
            parameters.Add(Expression.Convert(instance, isStatic ? typeof(object) : type));
            parameters.AddRange(parameterExpressions);
        }

        var invokeExpression = Expression.Invoke(
            Expression.Constant(openDelegate),
            parameters
        );
        Expression bodyExpression;
        if (methodInfo.ReturnType == typeof(void))
        {
            bodyExpression = Expression.Block(
                invokeExpression,
                Expression.Constant(null)
            );
        }
        else
        {
            bodyExpression = Expression.Convert(invokeExpression, typeof(object));
        }

        var lambda = Expression.Lambda<Func<object, object[], object>>(
            bodyExpression,
            instance,
            parameterArray
        );
        return lambda.Compile();
    }

    private static Type OpenDelegateType(Type type, bool isStatic, MethodInfo methodInfo, Type[] parameterTypes)
    {
        var types = new List<Type>();
        if (!isStatic)
        {
            types.Add(type);    // type of instance class
        }
        types.AddRange(parameterTypes);  // parameters
        if (methodInfo.ReturnType != typeof(void))
        {
            types.Add(methodInfo.ReturnType);   // return type
        }

        if (methodInfo.ReturnType == typeof(void))
        {
            return Expression.GetActionType(types.ToArray());
        }
        else
        {
            return Expression.GetFuncType(types.ToArray());
        }
    }
}

And version 2, which uses a different CreateMethodFunc:

public class DotNetExecutorFastV2
{
    private static readonly Dictionary<DotNetKey, Func<object, object[], object>> funcs = new Dictionary<DotNetKey, Func<object, object[], object>>();

    public static object ExecuteMethod(Type type, string name, bool isStatic, object instance, Type[] parameterTypes, object[] parameters)
    {
        var func = FindFunc(type, DotNetType.Method, name, isStatic, parameterTypes);
        return func(instance, parameters);
    }

    private static Func<object, object[], object> FindFunc(Type type, DotNetType dotNetType, string name, bool isStatic, Type[] parameterTypes)
    {
        var dotNetKey = new DotNetKey
        {
            type = type,
            dotNetType = dotNetType,
            name = name,
            isStatic = isStatic,
            parameterTypes = parameterTypes
        };
        if (funcs.TryGetValue(dotNetKey, out var func))
        {
            return func;
        }
        else
        {
            var newFunc = CreateMethodFunc(type, name, isStatic, parameterTypes);
            funcs.Add(dotNetKey, newFunc);
            return newFunc;
        }
    }

    private static Func<object, object[], object> CreateMethodFunc(Type type, string name, bool isStatic, Type[] parameterTypes)
    {
        var methodInfo = type.GetMethod(name, BindingFlags.Public | (isStatic ? BindingFlags.Static : BindingFlags.Instance), null, parameterTypes, null);

        var instance = Expression.Parameter(typeof(object), "instance");
        var parameterArray = Expression.Parameter(typeof(object[]), "parameterArray");
        var parameterExpressions = parameterTypes.Select((parameterType, index) => Expression.Convert(Expression.ArrayAccess(parameterArray, Expression.Constant(index)), parameterType));

        Expression callExpression;
        if (isStatic)
        {
            callExpression = Expression.Call(
                methodInfo,
                parameterExpressions
            );
        }
        else
        {
            callExpression = Expression.Call(
                Expression.Convert(instance, type),
                methodInfo,
                parameterExpressions
            );
        }
        Expression bodyExpression;
        if (methodInfo.ReturnType == typeof(void))
        {
            bodyExpression = Expression.Block(
                callExpression,
                Expression.Constant(null)
            );
        }
        else
        {
            bodyExpression = Expression.Convert(callExpression, typeof(object));
        }

        var lambda = Expression.Lambda<Func<object, object[], object>>(
            bodyExpression,
            instance,
            parameterArray
        );
        return lambda.Compile();
    }
}

What I'm basically doing is attempting to sort-of unlock the performance of strongly typed delegates by typing them as Func<object, object[], object> but somewhere I'm still losing performance, not sure if that happens because of the extra method call or because of the array access or because of something else.

So the question remains: How would I speed this up? Ideally I would get performance close to native and it needs to be completely dynamic as the C++ caller site would need to be able to invoke any C# (.NET) method.

over 4 years ago · Santiago Trujillo
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!