在 C# 中使用 lambda 进行流畅的接口配置

发布于 2025-01-01 05:15:31 字数 707 浏览 0 评论 0原文

许多开源项目使用 Configuration 类和 lambda 来阐明复杂对象的配置。以公共交通为例。一个简单的配置就像这样。

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

当您将鼠标悬停在 Visual Studio 中的 Initialize 上时,它会显示方法调用的参数是 Action。我想知道是否有人可以展示一个如何在类上使用此模式的简单示例。我什至不知道如何称呼这种类型的模式,而且我的“GoogleFu”尚未运行。在这种特殊情况下,我意识到该方法是在单例模式上运行的。但我同意它作为类的实例方法。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

此生挚爱伱 2025-01-08 05:15:31

Action 是一种方法,它接受 ServiceBusConfigurator 类型的单个参数,对该实例执行“操作”操作,并且不返回任何内容 (void< /代码>)。

.NET BCL(从 3.5 开始)附带预定义的通用委托签名:ActionAction(等等),用于不支持的方法返回一个值,以及 FuncFunc (等)用于接受方法零个或多个参数并返回 Tresult 类型的单个结果实例。

当您创建接受委托的方法时,您允许方法的调用者传递的不仅仅是数据参数 - 您的方法实际上将一部分责任委托给外部代码。在您的情况下,Bus.Initialize 创建 ServiceBusConfigurator (sbc) 的实例,然后使用 sbc< 调用指定的操作/code> 实例作为参数。

这基本上可以让您的方法控制配置类实例的生命周期。由调用者填写详细信息,但实际实例由您的类提供:

 // 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 方法创建并传递给 lambda。该方法返回后,该对象不再存在(或被包装在结果对象内)。

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:

  1. 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.

  2. 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).

一曲爱恨情仇 2025-01-08 05:15:31

补充一下其他人所说的,这是流畅界面的“入口点” 。使用 Action 回调来实现此目的的方法是一种隔离流畅接口的好方法,同时又非常可扩展。

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.

春庭雪 2025-01-08 05:15:31

这类似于工作单元模式,通常与事务和持久性场景相关,但似乎适合您的示例。

以下引文摘自Martin Fowler 对模式的定义

工作单元会跟踪您在业务事务期间所做的所有可能影响数据库的操作。当您完成后,它会计算出由于您的工作而需要执行的所有更改数据库的操作。

如果您将业务事务更改为初始化,将数据库更改为配置,您可以更好地了解正在发生的情况。此外,将操作(案例中的委托)视为原子操作:要么完全应用新配置,要么保持当前配置不变。

如前所述,操作本身并未显式触及 Bus。即使不知道所涉及的类的详细信息,我们也可以猜测这种交互是如何发生的:

  • 在调用操作之后,在 Initializa() 方法返回之前,可以读取 ServiceBusConfigurator (最有可能的);
  • Bus 可能会实现/扩展 ServiceBusConfigurator,以便 Initialize() 可以将 this 作为调用操作的参数传递(可能性较小);
  • Bus 可能是静态的,并且对 ServiceBusConfigurator 可见,而后者又可以在调用 ReceiveFrom() 时更改 Bus 的配置属性 (极其复杂,我希望这不太可能)。

这些是我现在突然想到的一些策略。可能会建议许多其他人!

This resembles the Unit of Work pattern, which is commonly associated with transactions and persistence scenarios, but seems to fit to your example.

The following citation was taken from Martin Fowler's definition of the pattern:

A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.

If you change business transaction for initialization and database for configuration, you can get a better idea of what's going on. Additionally, think of the action (the delegate in case) as an atomic operation: either the new configuration is fully applied, or the current configuration is kept unchanged.

As noted, the action itself doesn't explicitly touch Bus. Even without knowing the details of the involved classes, we can guess how this interaction occurs:

  • The ServiceBusConfigurator may be read after the action is invoked, before the Initializa() method returns (most likely);
  • Bus might implement/extend ServiceBusConfigurator, so that Initialize() can pass this as the argument of the invoked action (less likely);
  • Bus may be static and visible to ServiceBusConfigurator, which in turn can change the configuration properties of Bus upon a call to ReceiveFrom() (extremely convoluted and, I hope, very unlikely).

These are some strategies that just popped up my mind right now. Many others may be suggested!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文