Свободная конфигурация интерфейса с лямбдами в C # - PullRequest
6 голосов
/ 31 января 2012

Многие проекты с открытым исходным кодом используют класс Configuration и лямбда-выражения для пояснения настройки сложного объекта. Возьмите, например, Mass Transit . Простая конфигурация была бы такой:

Bus.Initialize(sbc =>
        {
            sbc.UseMsmq();
            sbc.VerifyMsmqConfiguration();
            sbc.VerifyMsDtcConfiguration();
            sbc.UseMulticastSubscriptionClient();
            sbc.ReceiveFrom("msmq://localhost/test");
        });

Когда вы наводите курсор мыши на Initialize в Visual Studio, он говорит, что параметром для вызова метода является Action<ServiceBusConfigurator>. Мне было интересно, может ли кто-нибудь показать простой пример того, как использовать этот шаблон в классе. Я даже не знаю, как назвать этот тип шаблона, и мой "GoogleFu" еще не работает. В этом конкретном случае я понимаю, что метод работает по одноэлементному шаблону. Но я согласен с тем, что это метод экземпляра в классе.

Ответы [ 3 ]

7 голосов
/ 31 января 2012

Action<ServiceBusConfigurator> - это метод, который принимает один параметр типа ServiceBusConfigurator, выполняет «действие», работающее с этим экземпляром, и ничего не возвращает (void).

.NET BCL (начиная с 3.5) поставляется с предопределенными общими сигнатурами делегатов: Action<T>, Action<T1, T2> (и т. д.) для методов, которые не возвращают значение, и Func<Tresult>, Func<T, Tresult> (и т. д.) для методов, принимающих ноль от болеепараметров и возвращая один экземпляр результата типа Tresult.

Когда вы создаете метод, который принимает делегат, вы позволяете вызывающим объектам вашего метода передавать больше, чем просто параметры данных - ваш метод фактически делегирует частьответственность за внешний код.В вашем случае Bus.Initialize создает экземпляр ServiceBusConfigurator (sbc), а затем вызывает указанное действие с экземпляром sbc в качестве параметра.

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

 // this is not actual mass transit source code
 public class BusCreator
 {
     public static IBus Initialize(Action<IConfiguration> action)
     {
         // create the config instance here
         IConfiguration config = CreateDefaultConfig();

         // let callers modify it
         action(config);

         // use the final version to build the result
         return config.Build()
     }
 }

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

 IBus result = BusCreator.Configure(cfg => cfg.BusType = BusType.MSMQ);

В строке выше следует отметить две вещи:

  1. Код внутри анонимного метода обернут внутри делегата, переданного методу.Он не выполняется до тех пор, пока метод Configure не вызовет его.

  2. Параметр cfg создается методом Configure и передается в лямбду.После возврата метода этот объект больше не существует (или обернут в результирующий объект).

1 голос
/ 31 января 2012

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

0 голосов
/ 31 января 2012

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

Следующая цитата была взята из определения шаблона Мартином Фаулером:

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

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

Как уже отмечалось, само действие явно не затрагиваетBus.Даже не зная деталей участвующих классов, мы можем догадаться, как происходит это взаимодействие:

  • ServiceBusConfigurator может быть прочитано после вызова действия, прежде чем метод Initializa() вернется (скорее всего);
  • Bus может реализовывать / расширять ServiceBusConfigurator, поэтому Initialize() может передавать this в качестве аргумента вызванного действия (менее вероятно);
  • Busможет быть статическим и видимым для ServiceBusConfigurator, что, в свою очередь, может изменить свойства конфигурации Bus при вызове ReceiveFrom() (чрезвычайно запутанный и, я надеюсь, очень маловероятный).

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

...