Снижение производительности Reflection.Emit

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

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

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

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

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

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

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

дескриптор ICustomTypeDescriptor, часть 1

ICustomTypeDescriptor, часть 2

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

Можно ли поддерживать этот сценарий? Есть ли какое-нибудь простое и легко реализуемое решение?

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


person lysergic-acid    schedule 11.01.2012    source источник
comment
Что мешает вам использовать объект Expando для таких обычаев?   -  person Osman Turan    schedule 12.01.2012
comment
Отредактировал мой вопрос - используя 3.5, для нас нет Expando.   -  person lysergic-acid    schedule 12.01.2012
comment
Тогда как насчет использования словаря и вызова метода с помощью Reflection API? Я использую то же самое в нашей CMS для централизации вызовов произвольных веб-методов для определенных методов класса.   -  person Osman Turan    schedule 12.01.2012
comment
Вызов метода не проблема. Проблема заключается в том, чтобы позволить пользователю вводить аргументы метода через PropertyGrid (или любой другой простой в использовании элемент управления пользовательского интерфейса).   -  person lysergic-acid    schedule 14.01.2012
comment
Разве вы не можете перебирать элементы PropertyGrid и собирать данные, предоставленные пользователем?   -  person Osman Turan    schedule 14.01.2012
comment
PropertyGrid привязывается к экземпляру объекта. Я хотел бы динамически создать некоторый объект, который имеет свойство для каждого аргумента, который получает мой метод. Аргумент может быть каким-то другим типом объекта сам по себе, поэтому это также необходимо учитывать...   -  person lysergic-acid    schedule 14.01.2012
comment
Я вообще не играл в PropertyGrid. Но, по вашей информации, я бы создал временный объект через Reflection. Кэширование временного объекта может повысить вашу производительность. Некоторые люди используют то же самое в .NET 3.5 вместо объекта Expando. Я не вижу других вариантов. Некоторые соответствующие источники находятся здесь: 1, 2, 3   -  person Osman Turan    schedule 14.01.2012
comment
И последнее, но не менее важное: вот код пример из .NET Framework.   -  person Osman Turan    schedule 14.01.2012


Ответы (2)


Да, это можно сделать (создать тип прокси со свойствами, соответствующими параметрам метода) с помощью 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

person Joe Enzminger    schedule 11.01.2012

Вот более или менее та же проблема и ее решение. Он написан для .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;
}
person Osman Turan    schedule 11.01.2012
comment
Осман, если вы используете отражение для вызова своих методов, вы получаете ОГРОМНОЕ снижение производительности. Вы должны рассмотреть (если вы еще этого не сделали, что-то вроде FastMethodInvoker (www.codeproject.com/KB/cs/FastMethodInvoker.aspx) - person Joe Enzminger; 12.01.2012
comment
@Джо: Спасибо! На самом деле мы вообще не чувствуем никакой медлительности (веб-методы так часто не вызываются). Более того, мы уже переходим на новую кодовую базу в нашем продукте. Так что он более-менее устарел. Но знание такой вещи, безусловно, хорошо. Еще раз спасибо. - person Osman Turan; 12.01.2012