2012-01-11 1 views
1

Я рассматриваю возможное решение проблемы, над которой я работаю (приложение .NET 3.5 WinForms).Снижение производительности Reflection.Emit

В нашем приложении у нас есть много методов (C#), аргументы которых введены пользователем приложения.

Примером может быть что-то вроде:

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

Где имя, дата в настоящее время вводится с помощью простого текстового поля. Мы хотели бы иметь преимущества богатых редакторов, защищенных паролем полей ввода, автозаполнения и т. Д.

Я хотел бы, чтобы пользовательский ввод выполнялся с использованием PropertyGrid, однако этот элемент управления может привязываться только к объекту, а не к аргументам.

Я прочитал оба отличные статьи из журнала MSDN относительно ProperyGrid:

ICustomTypeDescriptor, Part 1

ICustomTypeDescriptor, Part 2

Однако это, кажется, чтобы быть полезным в случае, когда объект, который будет связан с PropertyGrid известно заранее, что не является моим делом.

Можно ли поддерживать этот сценарий? Есть ли простое и простое решение?

Я думал об использовании Reflection.Emit для создания объекта «temp» во время выполнения, чьими свойствами будут аргументы метода. Я никогда не делал этого раньше (использование пространства имен Reflection.Emit), и я хотел бы знать, как это может повлиять на его использование? (действительно ли он компилирует код во время работы в памяти или как он работает?)

+0

Что остановить вас использовать объект EXPANDO для таких использований? –

+0

Отредактировал мой вопрос - используя 3.5, нет Expando для нас. –

+0

Тогда как насчет использования словаря и вызова метода с Reflection API? Я использую то же самое в нашей CMS для централизации вызовов произвольных веб-методов для определенных методов класса. –

ответ

1

Здесь более или менее такая же проблема и это решение. Он написан для .NET 3.5 и работает хорошо. Целью было централизовать все веб-методы в одном веб-сервисе (.asmx) и вызвать любые зарегистрированные методы из одного места. Код может быть намного меньше. Но из-за некоторого принудительного преобразования это немного длиннее.

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; 
} 
+1

Осман, если вы используете рефлексию для вызова своих методов, вы делаете ОГРОМНЫЙ удар производительности. Вы должны подумать (если у вас еще нет чего-то вроде FastMethodInvoker (www.codeproject.com/KB/cs/FastMethodInvoker.aspx) –

+0

@Joe: Спасибо! На самом деле мы не чувствуем никакой медлительности (веб-методы не являются так много). И еще мы уже переходим к новой базе кода в нашем продукте, поэтому она более или менее устарела, но, зная, что такая вещь, безусловно, хорошая вещь. Еще раз спасибо. –

3

Да, это возможно (создайте прокси-тип со свойствами, соответствующими параметрам метода), используя Reflection.Emit. После этого вы можете назначить экземпляр объекта-прокси в свой PropertyGrid, а затем использовать введенные значения для вызова метода. Однако то, что вы хотите сделать, не является тривиальным.

Я хотел бы указать вам документацию MSDN для TypeBuilder для примера создания типа с использованием Reflection.Emit.

Чтобы ответить на ваши вопросы о производительности, да, код скомпилирован «в память». Обычно вам нужно кэшировать сгенерированный тип в словаре, чтобы его можно было повторно использовать. Самый большой удар по производительности - это создание типа. Создание экземпляра типа может быть очень дешевым (в зависимости от того, как вы это делаете - Activator.CreateInstance() является самым медленным, что-то вроде этого:

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

, который позволяет создать новый экземпляр, просто вызывая

object obj = GetCreator()(); 

Используя эту модель, вы увидите пониженную производительность, когда ваше приложение просто начав, но по мере снижения пропускной способности кеша он будет работать почти так же хорошо, как встроенный код.

Вы можете использовать подобный метод для генерации Invokers - есть очень хороший пример здесь:

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