Отображение фабричных методов с анонимными функциями/делегатами с использованием словаря для более быстрого поиска?

В настоящее время у меня есть статический фабричный метод:

public static Book Create(BookCode code) {
    if (code == BookCode.Harry) return new Book(BookResource.Harry);
    if (code == BookCode.Julian) return new Book(BookResource.Julian);
    // etc.
}

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

Выполнение этих операторов if может стать узким местом в скорости. Но что, если мы сопоставим коды книг с анонимными функциями/делегатами? Что-то вроде следующего:

delegate Book Create();
private static Dictionary<BookCode, Delegate> ctorsByCode = new Dictionary<BookCode, Delegate>();
// Set the following somewhere
// not working!
ctorsByCode[BookCode.Harry] = Create () => { return new Book(BookResource.Harry); }
// not working!
ctorsByCode[BookCode.Julian] = Create () => { return new Book(BookResource.Julian); } 
public static Book Create(BookCode code) {
    return (Book)ctorsByCode[code].DynamicInvoke(null);
}

Как я могу заставить эти Create() => { строки действительно работать?

Стоит ли это того с точки зрения скорости, когда есть ‹50 книжных кодов (таким образом, ‹50 if-операторов)?

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

Обновить

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

5000000 iterations
CreateFromCodeIf ~ 9780ms
CreateFromCodeDelegate ~ 9990ms

person randomguy    schedule 29.08.2010    source источник


Ответы (3)


Как это:

private static readonly Dictionary<BookCode, Func<Book>> ctorsByCode = new Dictionary<BookCode, Func<Book>>();

...

ctorsByCode[BookCode.Harry] = () => new Book(BookResource.Harry);

public static Book Create(BookCode code) {
    return ctorsByCode[code]();
}

Тип Func<Book> — это предопределенный универсальный тип делегата, который можно использовать вместо создания собственного.
Кроме того, ваш словарь должен содержать конкретный тип делегата, а не базовый класс Delegate.

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

Вы также можете написать

ctorsByCode[BookCode.Harry] = new Func<Book>(() => new Book(BookResource.Harry));

Я бы рекомендовал вместо этого изменить ваш словарь, чтобы он содержал BookResources, например:

private static readonly Dictionary<BookCode, BookResource> codeResources = new Dictionary<BookCode, BookResource>();

...

codeResources[BookCode.Harry] = BookResource.Harry;

public static Book Create(BookCode code) {
    return new Book(codeResources[code]);
}
person SLaks    schedule 29.08.2010
comment
Вы доступны для найма? Сколько вы берете за час? :D Отличное решение. Мне нравится, что он немного компактнее, чем у Keltex. Альтернативное решение на самом деле очень хорошее. - person randomguy; 29.08.2010
comment
Я получаю, что делегат «System.Action‹Book›» не принимает 0 аргументов с обоими вышеуказанными методами. - person randomguy; 29.08.2010
comment
@randomguy: я забыл упомянуть, что Dictionary также должен быть объявлен readonly. (Это объявление влияет на переменную, а не на ссылку; это означает, что вы не можете написать codeResources = .... - person SLaks; 29.08.2010

Честно говоря, вы, вероятно, не заметите большой разницы в производительности между ними. Но если вы собираетесь использовать «функциональную» версию, я бы не просто использовал общий Delegate, а вместо этого передал бы делегат, который вы только что создали. Итак, это будет ваш (более простой) код:

delegate Book Create();
private static Dictionary<BookCode, Create> ctorsByCode 
    = new Dictionary<BookCode, Create>();

ctorsByCode[BookCode.Harry] = () => new Book(BookResource.Harry);
ctorsByCode[BookCode.Julian] = () => new Book(BookResource.Julian); 

public static Book Create(BookCode code) {
    return ctorsByCode[code]();
}
person Keltex    schedule 29.08.2010
comment
Хороший, сэр. Собираюсь принять ответ SLaks, но дал вам лодку. - person randomguy; 29.08.2010

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

  1. BookResource bookResourceEnum = (BookResource)Enum.Parse(typeof(BookResource),bookCode);

    вернуть новую книгу (bookResourceEnum);

  2. Или, что еще лучше, сделайте так, чтобы ваш статический фабричный метод Create принимал BookResource вместо BookCode, и пусть клиенты беспокоятся о преобразовании кода в перечисление, чтобы никто не мог передать код, который нельзя сопоставить с BookResource.

Серьезно, если бы вы написали мне код с 50 "если" или еще хуже с 50 "делегатами", мы бы "поговорили". Старайтесь избегать преждевременной оптимизации (если она есть), когда вы не уверены, что это ваше узкое место. Удобочитаемость и ремонтопригодность должны стоять на первом месте.

person epitka    schedule 30.08.2010
comment
Привет! Спасибо за отзыв. BookResource — это класс ресурсов с учетом культуры, автоматически сгенерированный из файла .resx, поэтому нет смысла использовать его так, как вы предлагаете. Или, по крайней мере, это не имело для меня смысла. Я согласен со всеми последними пунктами, однако я чувствую, что метод делегирования был бы довольно изящным... если бы накладные расходы не были такими большими. - person randomguy; 31.08.2010