lysergic-acid
lysergic-acid

Reputation: 20050

Performance penalty of Reflection.Emit

I am considering a possible solution to an issue i'm working on (.NET 3.5 WinForms application).

In our app, we have many methods (C#) whose arguments are entered by the user of the application.

An example would be something like:

public void DoSomething(string name, DateTime date)
{
   // ...
}

Where name, date is currently entered using a simple textbox. We would like to have the benefits of rich editors, password protected input boxes, autocomplete, and more.

I would like the user's input to be made using a PropertyGrid, however this control can only bind to an object, and not arguments.

I have read both excellent articles from MSDN magazine regarding the ProperyGrid:

ICustomTypeDescriptor, Part 1

ICustomTypeDescriptor, Part 2

However this seems to be helpful in a scenario where the object that will be bound to the PropertyGrid is known in advance, which is not my case.

Can this scenario be supported? Is there any simple and easy to implement solution?

I thought of using Reflection.Emit to create a "temp" object at runtime, whose properties would be the method's arguments. I have never done this before (use of Reflection.Emit namespace) and i would like to know the performance penalty of using it? (does it actually compile the code at runtime in-memory or how does it work?)

Upvotes: 2

Views: 1781

Answers (2)

Joe Enzminger
Joe Enzminger

Reputation: 11190

Yes, it is possible to do this (create a proxy type with properties corresponding to method parameters) using Reflection.Emit. Once you have this, you could assign an instance of the proxy object to your PropertyGrid, and then use the values entered to call the method. What you want to do, however, is not trivial.

I would point you to the MSDN documentation for TypeBuilder for an example of creating a type using Reflection.Emit.

To answer your questions about performance, yes, the code is compiled "in memory". What you would typically want to do is cache the generated type in a dictionary so it can be reused. The biggest performance hit is on Type generation. Creating an instance of the type can be made to be very cheap (depending on how you do it - Activator.CreateInstance() being the slowest, something like this:

private Func<T> GetCreator()
    {
        if (_Creator == null)
        {
            Expression body;
            var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
            var defaultConstructor = typeof(T).GetConstructor(bindingFlags, null, new Type[0], null);
            if (defaultConstructor != null)
            {
                // lambdaExpression = () => (object) new TClass()
                body = Expression.New(defaultConstructor);
            }
            else
            {
                // lambdaExpression = () => FormatterServices.GetUninitializedObject(classType)
                var getUnitializedObjectMethodInfo = typeof(FormatterServices).GetMethod("GetUninitializedObject", BindingFlags.Public | BindingFlags.Static);
                body = Expression.Call(getUnitializedObjectMethodInfo, Expression.Constant(typeof(T)));
            }
            var lambdaExpression = Expression.Lambda<Func<T>>(body);
            _Creator = lambdaExpression.Compile();
        }
        return _Creator;
    }

which allows you to create a new instance by simply calling

object obj = GetCreator()();

Using this pattern, you'll see degraded performance when your application is just getting started, but as the cache misses decrease it will perform almost as good as inline code.

You can use a similar method for generating Invokers - there is a pretty good example here:

http://www.codeproject.com/KB/cs/FastMethodInvoker.aspx

Upvotes: 3

Osman Turan
Osman Turan

Reputation: 1371

Here is more or less same problem and it's solution. It's written for .NET 3.5 and works well. The goal was centralize all web methods in a single web service (.asmx) and call any registered methods from a single location. The code could be much more smaller. But, due to some forced conversion, it's a bit long.

public object ExecuteMethod(string moduleName, string methodName, object[] arguments)
{
  CmsModuleMethodInfo methodInfo = CmsModuleManager.GetModuleMethod(moduleName, methodName);
  ...

  ParameterInfo[] paramInfo = methodInfo.Method.GetParameters();
  Object[] parameters = new Object[paramInfo.Length];
  Type[] paramTypes = paramInfo.Select(x => x.ParameterType).ToArray();
  for (int i = 0; i < parameters.Length; ++i)
  {
    Type paramType = paramTypes[i];
    Type passedType = (arguments[i] != null) ? arguments[i].GetType() : null;

    if (paramType.IsArray)
    {
      // Workaround for InvokeMethod which is very strict about arguments.
      // For example, "int[]" is casted as System.Object[] and
      // InvokeMethod raises an exception in this case.
      // So, convert any object which is an Array actually to a real Array.
      int n = ((Array)arguments[i]).Length;
      parameters[i] = Array.CreateInstance(paramType.GetElementType(), n);
      Array.Copy((Array)arguments[i], (Array)parameters[i], n);
    }
    else if ((passedType == typeof(System.Int32)) && (paramType.IsEnum))
    {
      parameters[i] = Enum.ToObject(paramType, (System.Int32)arguments[i]);
    }
    else
    {
      // just pass it as it's
      parameters[i] = Convert.ChangeType(arguments[i], paramType);
    }
  }

  object result = null;
  try
  {
    result = methodInfo.Method.Invoke(null, parameters);
  }
  catch (TargetInvocationException e)
  {
    if (e.InnerException != null)
    {
      throw e.InnerException;
    }
  }

  return result;
}

Upvotes: 1

Related Questions