Many open source projects use a Configuration class and lambda's to clarify configuring a complex object. Take Mass Transit for example. A simple configuration would be like so.
Bus.Initialize(sbc =>
{
sbc.UseMsmq();
sbc.VerifyMsmqConfiguration();
sbc.VerifyMsDtcConfiguration();
sbc.UseMulticastSubscriptionClient();
sbc.ReceiveFrom("msmq://localhost/test");
});
When you hover over Initialize
in Visual Studio it says the parameter for the method call is Action<ServiceBusConfigurator>
. I was wondering if anyone could show a simple example of how to use this pattern on a class. I don't even know what to call this type of pattern and my "GoogleFu" is not working as of yet. In this particular case I realize the method is operating on a singleton pattern. But I am ok with it being an instance method on a class.
An Action<ServiceBusConfigurator>
is a method which accepts a single parameter of type ServiceBusConfigurator
, does an "action" operating on that instance, and returns nothing (void
).
.NET BCL (starting from 3.5) comes with predefined generic delegate signatures: Action<T>
, Action<T1, T2>
(etc.) for methods which don't return a value, and Func<Tresult>
, Func<T, Tresult>
(etc.) for methods accepting zero of more parameters and returning a single result instance of type Tresult
.
When you create a method which accepts a delegate, you allow callers of your method to pass more than just data parameters - your method actually delegates a part of responsibility to external code. In your case, Bus.Initialize
creates an instance of ServiceBusConfigurator
(sbc
), and then calls the specified action with the sbc
instance as the parameter.
This basically lets your method control the lifetime of the configuration class instance. It is up to the caller to fill in the details, but actual instance is provided by your class:
// 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()
}
}
The benefit is that your built instance (the imaginary IBus
in this case) cannot be modified further. Configuration instance is only created shortly, passed to an external method, and then used to create an immutable final object:
IBus result = BusCreator.Configure(cfg => cfg.BusType = BusType.MSMQ);
Two things to note in the line above:
The code inside the anonymous method is wrapped inside a delegate passed to the method. It is not executed until the Configure
method actually calls it.
The cfg
parameter is created by the Configure
method and passed to the lambda. After the method returns, this object doesn't exist anymore (or is wrapped inside the resulting object).
To add to what others have said, this is an "entry point" into a fluent interface. The approach of using an Action callback to achieve this is a nice way of isolating the fluent interface in a way that is at the same time very extensible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With