Переключатель или словарь при назначении новому объекту


12

В последнее время я предпочел отображать отношения 1-1, используя Dictionariesвместо Switchоператоров. Я считаю, что это немного быстрее писать и легче мысленно обрабатывать. К сожалению, при сопоставлении с новым экземпляром объекта я не хочу определять его следующим образом:

var fooDict = new Dictionary<int, IBigObject>()
{
    { 0, new Foo() }, // Creates an instance of Foo
    { 1, new Bar() }, // Creates an instance of Bar
    { 2, new Baz() }  // Creates an instance of Baz
}

var quux = fooDict[0]; // quux references Foo

Учитывая эту конструкцию, я потратил впустую циклы ЦП и память, создавая 3 объекта, делая то, что могли содержать их конструкторы, и в итоге использовал только один из них. Я также считаю, что сопоставление других объектов fooDict[0]в этом случае приведет к тому, что они будут ссылаться на одну и ту же вещь, а не на создание нового экземпляра, Fooкак предполагалось. Решение было бы использовать вместо этого лямбда:

var fooDict = new Dictionary<int, Func<IBigObject>>()
{
    { 0, () => new Foo() }, // Returns a new instance of Foo when invoked
    { 1, () => new Bar() }, // Ditto Bar
    { 2, () => new Baz() }  // Ditto Baz
}

var quux = fooDict[0](); // equivalent to saying 'var quux = new Foo();'

Это доходит до того, что это слишком запутанно? Это легко пропустить ()в конце. Или отображение на функцию / выражение является довольно распространенной практикой? Альтернативой было бы использовать переключатель:

IBigObject quux;
switch(someInt)
{
    case 0: quux = new Foo(); break;
    case 1: quux = new Bar(); break;
    case 2: quux = new Baz(); break;
}

Какой вызов более приемлем?

  • Словарь, для быстрого поиска и меньше ключевых слов (case and break)
  • Переключатель: чаще встречается в коде, не требует использования объекта Func <> для косвенного обращения.

2
без лямбды у вас будет один и тот же экземпляр, возвращаемый каждый раз, когда вы выполняете поиск с тем же ключом (как в fooDict[0] is fooDict[0]). как с лямбдой, так и с переключателем это не так
трещотка урод

@ratchetfreak Да, я действительно понял это, когда набирал пример. Я думаю, что где-то это записал.
KChaloux

1
Я предполагаю, что тот факт, что вы явно поместили его в константу, означает, что вам нужно, чтобы созданный объект был изменчивым. Но если однажды вы сможете сделать их неизменяемыми, то возврат объекта напрямую будет лучшим решением. Вы можете поместить dict в поле const и нести стоимость создания только один раз во всем приложении.
Лоран Бургало-Рой

Ответы:


7

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

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

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

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

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


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

1
@KChaloux: Конечно, если вы использовали только шаблон фабричного метода, вы case 0: quux = new Foo(); break;становитесь case 0: return new Foo();откровенно простыми и намного проще для чтения, чем{ 0, () => new Foo() }
pdr

@pdr Это уже несколько мест в коде. Вероятно, есть веская причина для создания фабричного метода для объекта, который вдохновил этот вопрос, но я подумал, что его было достаточно интересно задать самостоятельно.
KChaloux

1
@KChaloux: Признаюсь, я не заинтересован в недавней одержимости заменой switch / case на словарь. Я еще не видел случая, когда упрощение и изоляция переключателя в его собственном методе не были бы более эффективными.
Pdr

@pdr Obsession - сильное слово для использования здесь. Больше внимания при принятии решения о том, как работать с одноразовыми сопоставлениями между значениями. Я согласен, что в тех случаях, когда это повторяется, лучше всего выделить творческий метод.
KChaloux

7

C # 4.0 предоставляет вам Lazy<T>класс, который похож на ваше собственное второе решение, но более явно выкрикивает «Ленивая инициализация».

var fooDict = new Dictionary<int, Lazy<IBigObject>>()
{
    { 0, new Lazy(() => new Foo()) }, // Returns a new instance of Foo when invoked
    { 1, new Lazy(() => new Bar()) }, // Ditto Bar
    { 2, new Lazy(() => new Baz()) }  // Ditto Baz
}

Ох, я этого не знал.
KChaloux


2
Однако, как только Lazy.Value вызывается, он использует тот же экземпляр для своей жизни. Посмотрите Ленивую Инициализацию
Дэн Лайонс

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

ОП заявляет, что ему это нужно для создания новых экземпляров каждый раз. Второе решение с лямбдами и третье решение с коммутатором делают это, в то время как первое решение и реализация Lazy <T> этого не делают.
Дэн Лайонс

2

Стилистически я думаю, что читаемость одинакова между ними. Проще внедрить зависимости с помощью Dictionary.

Не забывайте, что вы должны проверить, существует ли ключ при использовании Dictionary, и должны предоставить запасной вариант, если это не так.

Я бы предпочел switchоператор для статических путей кода и Dictionaryдля динамических путей кода (где вы можете добавлять или удалять записи). Компилятор может выполнить некоторые статические оптимизации с тем, switchчто он не может с Dictionary.

Интересно, что этот Dictionaryшаблон - то, что люди иногда делают в Python, потому что в Python отсутствует switchутверждение. В противном случае они используют цепочки if-else.


1

В общем, я бы предпочел ни того, ни другого.

Все, что потребляет это должно работать с Func<int, IBigObject>. Тогда источником вашего сопоставления может быть Словарь или метод, который имеет оператор switch, вызов веб-службы или какой-либо поиск файла ... что угодно.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.