Фабрика методов - случай против отражения - PullRequest
6 голосов
/ 20 ноября 2010

Я наткнулся на какой-то код на днях, и я подумал, что это лучший способ сделать это.У нас есть метод, который берет строку из некоторых данных веб-формы и делает что-то с объектом на основе переданной строки. В настоящее время он использует отражение, чтобы выяснить, какое действие предпринять, но мне было интересно, будет ли оператор switch лучше.

Пример:

Редактировать: я добавил третий вариант для делегатов, как отметил Lucerno

public class ObjectManipulator
{
    private void DoX(object o) { }
    private void DoY(object o) { }
    private void DoZ(object o) { }

    public void DoAction(string action, object o)
    {
        switch (action)
        {
            case "DoX":
                DoX(o);
                break;
            case "DoY":
                DoY(o);
                break;
            case "DoZ":
                DoZ(o);
                break;
            default:
                throw new Exception(string.Format(
                    "Cannot locate action:{0}", action));
        }
    }

    public void DoActionViaReflection(string action, object o)
    {
        MethodInfo method = typeof(ObjectManipulator).
            GetMethod(action, new Type[] { typeof(object) });
        if (method == null)
        {
            throw new Exception(string.Format(
                "Cannot locate action:{0}", action));
        }
        else
        {
            method.Invoke(this, new object[] { o });
        }
    }
    private Dictionary<string, Action<object>> _methods;
    public ObjectManipulator()
    {
        _methods = new Dictionary<string, Action<object>>()
        {
            {"DoX", o => DoX(o)},
            {"DoY", o => DoY(o)},
            {"DoZ", o => DoZ(o)}
        };
    }
    public void DoActionViaDelegates(string action, object o)
    {
        if (!_methods.ContainsKey(action))
        {
            throw new Exception(string.Format(
                "Cannot locate action:{0}", action));
        }
        else
        {
            _methods[action](o);
        }
    }

}

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

Будет ли один метод работать значительно лучше другого?

Изменилась бы производительность, если бы было 100 различных действий вместо всего лишь 3?

Что вы, скорее, видите в своем коде, если читаете его?

Ответы [ 4 ]

4 голосов
/ 20 ноября 2010

Первый случай почти всегда будет быстрее.Однако его производительность обусловлена ​​тем фактом, что он может быть ранним во время компиляции, но это также является его самым большим недостатком: этот подход не может, например, обрабатывать динамически загруженные сборки, и он гораздо более подвержен ошибкам, так как онимператив и не декларативный.(Например, забыть недавно реализованное действие - это то, что может произойти быстро.)

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

  • Фаза обнаружения: используйте отражение, чтобы найти членов (используя атрибуты, интерфейсы, подписи и/ или соглашения о кодировании).В вашем случае у вас всегда одна и та же подпись, поэтому используемым делегатом будет Action<object>.Добавьте этих членов в экземпляр Dictionary<string, Action<object>>, создав делегата из MethodInfo, используя CreateDelegate().

  • Фаза вызова: получите делегата через его ключи вызвать его, что очень просто (здесь предполагается, что словарь называется methods): methods[action](o)

3 голосов
/ 20 ноября 2010

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

var Actions = new Dictionary<String, Action<object>>();
Actions["DoX"] = x => DoX(x); 
Actions["DoY"] = x => DoY(x); 
Actions["DoZ"] = x => DoZ(x); 

и позже

public void DoAction(string action, object o)
{
    Actions[action](o);
}

Я думаю, это работает лучше, если у вас много случаев, потому что в словаре есть поиск по хешу

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

2 голосов
/ 20 ноября 2010

Вы можете исправить ответ @ user287107 следующим образом:

var Actions = new Dictionary<String, Action<object>>();
Actions["DoX"] = DoX;
Actions["DoY"] = DoY;
Actions["DoZ"] = DoZ;

Это фактически делает фазу обнаружения ответа @ Lucero явно, что может быть полезно, если имена не всегда совпадают.

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

2 голосов
/ 20 ноября 2010

Производительность не должна вас беспокоить, если вы не профилируете ее, и это является узким местом.Более важным, IMO, является то, что вы теряете безопасность статического типа и анализ в версии с отражением.Во время компиляции невозможно проверить, не вызваны ли эти методы действия DoX, DOY и т. Д.Это может или не может быть проблемой для вас, но это будет моей самой большой проблемой.

Кроме того, количество действий совершенно не подходит для выполнения версии отражения.GetMethod не замедляется, когда в вашем классе много учеников.

...