如何使用lambda指定构造函数参数
本文关键字:构造函数 参数 何使用 lambda | 更新日期: 2023-09-27 18:26:12
我对实现lambda和表达式并不太熟悉,但我在MVC中已经多次使用这种语法,其中lambda标识对象上的属性:
Html.Label(model => model.Foo)
在我的应用程序中,我使用Ninject条件绑定来提供Settings
类的实例,当我请求Class
的实例时会注入该实例。我的Class
看起来像这样:
public class Class
{
private readonly Settings settings;
public Settings Settings { get { return settings; } }
public Class(Settings settings)
{
this.settings = settings;
}
}
我有一些类似这样的代码来获得Class
的实例。我知道这是服务定位器的反模式,但由于其他限制,我们在这种情况下别无选择:
var settings = new Settings();
var instance = Ioc.Instance.Get<Class>("settings", settings);
我想把它重构成这样,这样它就是强类型的,使用lambda来指定我提供的构造函数上的哪个参数:
var settings = new Settings();
var instance = Ioc.Instance.Get<Class>(x => x.settings, settings);
那么,这可能吗?代码会是什么样子?
概念上缺少工厂(工厂接口),因此应该引入它以避免直接使用容器。
NinjectFactory(工厂接口)扩展可以用于创建实例,如下所示:
声明工厂接口:
public interface IFactory
{
Class Create(Settings settings);
}
向组合根添加绑定:
kernel.Bind<IFactory>().ToFactory();
使用工厂获取一个实例:
var settings = new Settings();
var factory = Ioc.Instance.Get<IFactory>();
var instance = factory.Create(settings);
请参阅ninject/inject.extensions.factory了解替代方案。
构造函数参数名称和表达式的问题是,只有当表达式包含构造函数的所有参数时,它才有效/完整。现在,我假设你想注入一些参数(让ninject处理它们),对于一两个特定的参数,你想传递一个值,假设它看起来像:
public interface IFoo { }
public class Foo : IFoo
{
public Foo(IServiceOne one, IServiceTwo two, string parameter) {...}
}
Ninject支持ctor表达式,但仅用于绑定,它们的工作方式如下:
IBindingRoot.Bind<IFoo>().ToConstructor(x =>
new Foo(x.Inject<IServiceOne>(), x.Inject<IServiceTwo>(), "staticArgument");
因此,您不必只指定您感兴趣的"staticArgument",还必须指定IServiceOne
和IServiceTwo
。如果构造函数发生更改怎么办?这个电话也需要调整!只传递一个简单的参数需要做很多工作。
现在,如果你仍然想这样做,我建议你看看ToConstructor
代码,并为Get
调用创建一个类似的扩展,它将翻译一些调用
IResolutionRoot.Get<IFoo>(x =>
new Foo(
x.Ignore<IServiceOne>(),
x.Ignore<IServiceTwo>(),
x.UseValue("mystring"));
至
IResolutionRoot.Get<IFoo>(new ConstructorArgument("parameter", "mystring"));
然而,我建议使用@Sergey Brunov的答案,并使用Ninject.Extensions.Factory。现在我认为你会说这不好,因为你仍然需要指定参数名称,。。这不是重构的安全和麻烦(没有代码完成…)。
然而,这个问题有一个解决方案:您可以使用类型匹配的参数,而不是使用与参数名称"匹配"的构造函数参数。好的,有个问题。如果有多个相同类型的参数,。。这行不通。但我认为这种情况很少见,您仍然可以引入一个容器数据类来解决它:
public class FooArguments
{
string Argument1 { get; set; }
string Argument2 { get; set; }
}
现在如何使用类型匹配?有两种方法:
- 使用
Func<string, IFoo>
工厂。只需将Func<string, IFoo>
注入您想要创建的位置和IFoo
- 扩展Factory扩展。是的,你没听错;-)其实没那么难。您"只"需要实现一个自定义
IInstanceProvider
(另请参阅http://www.planetgeek.ch/2011/12/31/ninject-extensions-factory-introduction/)所以你可以这样做:
public interface IFooFactory
{
IFoo Create([MatchByType]string someParam, string matchByName);
}
(===>使用一个属性告诉工厂扩展如何将参数传递给Get<IFoo>
请求)。
看看下面的文章-http://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/
特别是
public static class Extensions
{
public static string GetPropertyName<T,TReturn>(this Expression<Func<T,TReturn>> expression)
{
MemberExpression body = (MemberExpression)expression.Body;
return body.Member.Name;
}
}
注意-此方法可能不适用于使用表达式来指示类的属性名的所有可能方式,因此可能需要根据您的需求(需要它的通用性)进行增强。
但本质上,一旦你有了这个helper方法,你的调用就会变成
var settings = new Settings();
Ioc.Instance.Get<Class>(GetPropertyName(x => x.settings), settings);