Эффективное использование отражения в C # - PullRequest
9 голосов
/ 28 октября 2011

Я пишу библиотеку на C #, которую позже буду использовать для приложения, и я хочу, чтобы библиотека была настолько эффективной, насколько это возможно (т.е. не слишком усложняйте вещи, чтобы сделать их более эффективными) , Однако у меня есть вопрос о том, как наиболее эффективно использовать рефлексию в классе / методах и чтобы проиллюстрировать вопрос, который я значительно упростил для своего класса, до следующего:

class MyClass
{
    private static Dictionary<string, object> methods;

    public void Method1()
    {
        // Do something.
    }

    public void Method2()
    {
        // Do something else.
    }
}

Теперь, что я хочу, это внутри класса (приватный метод еще не создан), возьмите строку, содержащую имя метода, а затем запустите метод, простой как этот. Самый простой способ сделать это - просто посмотреть на имя, получить метод с таким именем и выполнить его, но это заставляет меня много раз использовать рефлексию. Этот частный метод может быть вызван тысячи или десятки тысяч раз, и он должен быть быстрым. Поэтому я предложил два возможных решения. Как вы, вероятно, видите, я добавил статический словарь, содержащий строку-> объект (заменив объект реальным типом, просто записал объектную причину, которая работает с обоими моими примерами). Затем я бы добавил статический конструктор, который проходит через класс и добавляет все методические сведения в словарь методов. И тогда возникает вопрос, при создании нового экземпляра класса, следует ли мне создавать связанные методы для делегатов и помещать их в нестатический приватный текст, или я должен просто запускать методы с использованием MethodInfo в словаре методов

Средний вариант использования создаст 10 экземпляров этого класса и вызовет более 1000 вызовов метода, который должен запускать либо Method1, либо Method2, в зависимости от строкового аргумента (и нет, switch-case не является опцией из-за расширяемость класса, как сказано, это была упрощенная версия). Каков наиболее эффективный способ достижения этого?

Ответы [ 8 ]

12 голосов
/ 28 октября 2011

Очевидно, что никто не может ответить на вопрос, фактически не попробовав его и не выполнив тесты производительности, чтобы увидеть, достигнута ваша цель или нет.

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

Мое предложение будет начинаться с предложенного вами решения: один раз создайте кэш информации метода:

class MyClass
{
    static Dictionary<string, MethodInfo> cache = new ...
    public void InvokeByName(string name)
    {
        MethodInfo methodInfo = GetMethodInfoFromCache(name);
        methodInfo.Invoke(this, new object[] {});
    }

Когда его попросят вызвать метод, идентифицируемый строкой в ​​конкретном экземпляре как получатель, найдите информацию о методе по имени, а затем вызовите его с данным получателем. Измерьте эффективность этого и посмотрите, соответствует ли он вашей цели. Если это так, отлично; не теряйте больше своего драгоценного времени, пытаясь сделать что-то более быстрое, уже достаточно быстрое.

Если это не достаточно быстро, вот что я сделаю:

class MyClass
{
    static Dictionary<string, Action<MyClass>> cache = new ...
    public void InvokeByName(string name)
    {
        GetActionFromCache(name).Invoke(this);            
    }

Так что же делает GetActionFromCache? Если в кеше уже есть действие, мы закончили. Если нет, то получите MethodInfo через Reflection. Затем используйте библиотеку Expression Tree для построения лямбды:

var methodInfo = SomehowGetTheMethodInfo(name);
// We're going to build the lambda (MyType p)=>p.<named method here>()    
var p = Expression.Parameter(typeof(MyType), "p"));
var call = Expression.Call(p, methodInfo);
var lambda = Expression.Lambda<Action<MyType>>(call, p);
var action = lambda.Compile();

И теперь у вас в руках есть действие, которое вы можете вызвать с экземпляром. Вставьте эту вещь в кеш.

Это, кстати, на невероятно упрощенном уровне, как "динамический" работает в C # 4. Наша проблема чрезвычайно усложняется тем, что нам приходится иметь дело с получателем и аргументами, имеющими любой тип. У вас это очень легко сравнительно.

4 голосов
/ 28 октября 2011

Поскольку вы получите все экземпляры MethodInfo и имя для сопоставления их (предположительно через свойство MethodInfo.Name, вы можете пойти еще дальше и создать скомпилированное лямбда-выражение в форме делегата, которое вы можете выполнить.

Во-первых, предполагается, что все ваши методы будут иметь одинаковую подпись. В данном случае это Action<T> делегат . При этом ваш словарь будет выглядеть так:

// No need to have the dictionary **not** readonly
private static readonly IDictionary<string, Action<MyClass>> methods =
    new Dictionary<string, Action<MyClass>>;

Затем в статическом конструкторе вы использовали бы отражение, чтобы получить все MethodInfo экземпляры:

static MyClass()
{
    // Cycle through all the public instance methods.
    // Should filter down to make sure signatures match.
    foreach (MethodInfo methodInfo in typeof(MyClass).
        GetMethods(BindingFlags.Public | BindingFlags.Instance))
    {
        // Create the parameter expression.
        ParameterExpression parameter = Expression.
            Parameter(typeof(MyClass), "mc");

        // Call the method.
        MethodCallExpression body = Expression.Call(pe, methodInfo);

        // Compile into a lambda.
        Action<MyClass> action = Expression.Lambda<Action<MyClass>>(
            body, parameter).Compile();

        // Add to the dictionary.
        methods.Add(methodInfo.Name, action);
    }
}

Тогда ваш приватный метод будет выглядеть так:

private void ExecuteMethod(string method)
{
    // Add error handling.
    methods[method]();
}

Преимущество здесь в том, что вы получаете производительность скомпилированного кода, платя при этом очень маленькую цену (IMO) за сложность кода (при создании делегата). Конечно, есть небольшие издержки при вызове кода через делегат, но это было значительно улучшено (это должно было произойти с введением LINQ, поскольку они будут выполняться много-много раз).

2 голосов
/ 28 октября 2011

Из вашего комментария:

Ну, проще говоря, он подключен к серверу, сервер отправляет команды в виде ... , и имя определяет, какие функции должны быть выполнены.Я хочу иметь возможность просто добавлять функции с совпадающими именами (или, скорее, я создал атрибут, который позволяет мне именовать методы, отличные от их имени метода, потому что имена команд могут быть числовыми), потому что список имен будет слишком длинными я не хочу делать переключение.

Вы можете решить эту проблему с помощью простого командного интерфейса и фабрики на основе таблиц для соответствующего экземпляра (или типа,если экземпляры команд не могут быть использованы повторно).

public interface ICommand {
  void Execute();
}

public class Processor {
  private static Dictionary<string, ICommand> commands;
  static Processor() {
    // create and populate the table
  }
  public void ExecuteCommand(string name) {
    // some validation...
    commands[name].Execute();
  }
}

Нет отражения.

Чтобы создать новую команду, просто создайте новый класс, который реализует ICommand и добавьте соответствующую строку втаблица commands внутри статического конструктора Processor.

public class FooCommand : ICommand {
  public void Execute() {
    // foo away!
  }
}

...

public class Processor {
  static Processor() {
    ...
    commands["foo"] = new FooCommand();
    ...
  }
}

У этой конструкции есть много преимуществ, помимо производительности.Ваши команды изолированы друг от друга, изменения в одной команде или создание новых команд не влияют на другие команды.Они лучше тестируются и их легче поддерживать.Даже процессор может быть закрыт (например, OCP ), если вы можете поддерживать свою таблицу, например, в конфигурационном файле или базе данных.

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

2 голосов
/ 28 октября 2011

Если бы у меня был выбор, я бы, вероятно, согласился с предложением Хенка и использовал бы динамику. Вызовы методов невероятно быстрые (намного быстрее, чем обычные отражения и почти как обычные вызовы методов).

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

Однако, если вы хотите поддерживать 3.5 или оставить свои параметры открытыми, и у вас нет возражений против использования сторонней библиотеки , тогда это все еще может быть довольно легко достигнуто:

void Invoke( string methodName )
{
    this.CallMethod( methodName );
}

CallMethod создает делегат DynamicMethod для вызова этого конкретного метода и кэширует его в случае повторного вызова того же метода. Существуют также расширения для вызова методов с параметрами и множество других полезных помощников отражения.

Если вы предпочитаете кэшировать делегата самостоятельно (для этого мы используем ConcurrentDictionary и WeakReferences, что означает, что он может собирать мусор), просто вызовите DelegateForCallMethod.

Библиотека поддерживает как 3.5, так и 4.0. WP7 не поддерживает Reflection.Emit и поэтому не может использовать генерацию IL и DynamicMethod. Однако WP 7.5 поддерживает это (но Fasterflect, как называется библиотека, пока не поддерживает его).

1 голос
/ 28 октября 2011

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

class MyClass
{
    private static Dictionary<string, Action<MyClass>> methods;

    public void Method1()
    {
        // Do something.
    }

    public void Method2()
    {
        // Do something else.
    }
    static MyClass(){
       methods = new Dictionary<string, Action<MyClass>>();
       foreach(var method in typeof(MyClass).GetMethods(
               BindingFlags.Public | BindingFlags.Instance)
       )
        {
            methods.Add(
                method.Name,
                Delegate.CreateDelegate(typeof(Action<MyClass>),method) 
                  as Action<MyClass>);
        }
    }
}

Этот код имеет то преимущество, что не использует генерацию кода. Однако, если у вас есть методы с разными сигнатурами, вам потребуется другой подход. Здесь мы создаем делегатов с открытым экземпляром. (Обратите внимание, что это не всегда работает правильно, если MyClass является структурой или любой из этих методов является универсальным виртуальным методом).

0 голосов
/ 28 октября 2011

Рассматривали ли вы использование Генерация кода и текстовых шаблонов T4 ?

http://msdn.microsoft.com/en-us/library/bb126445.aspx
http://msdn.microsoft.com/en-us/library/bb126478.aspx

Тогда вы можете использовать оператор case.

Что-то вроде

partial Class MyClass
{
    public void Exec(string funcToExec)
    {
        swtich(funcToExec)
        {
            <#
            foreach(MethodInfo mi in 
                typeof(MyClass).GetMethods(BindingFlags.Public | BindingFlags.Static)
            { 
                if(mi.Name != "Exec"){
            #>
            case : "<#= mi.Name #>"
                <#= mi.Name #>();
            <#
            }}
            #>
        }
    }
}
0 голосов
/ 28 октября 2011

Вызывает MethodInfo медленно.Поэтому я думаю, что создание нового словаря для каждого экземпляра должно быть достаточно хорошим.Другой вариант - создать делегата, который принимает экземпляр (Action<MyClass>) с помощью выражений (и затем сохранить их в статическом словаре):

MethodInfo method = typeof(MyClass).GetMethod("Method1");

var parameter = Expression.Parameter(typeof(MyClass));

var call = Expression.Call(parameter, method);

var lambda = Expression.Lambda<Action<MyClass>>(call, parameter);

Action<MyClass> del = lambda.Compile();
0 голосов
/ 28 октября 2011

Самый быстрый способ сделать что-то - это вообще не делать.Рассматриваете ли вы просто создание интерфейса вроде:

interface ICallMe 
{
 void CallByName(string name, object args);
}

Таким образом, если некоторые реализации хотят быть безумно умными, они могут выполнять рефлексию + кэширование + генерацию IL, другие могут просто использовать if / switch.

Недостатки - значительно меньше удовольствия от реализации и отладки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...