如何使用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);

那么,这可能吗?代码会是什么样子?

如何使用lambda指定构造函数参数

概念上缺少工厂(工厂接口),因此应该引入它以避免直接使用容器。

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",还必须指定IServiceOneIServiceTwo。如果构造函数发生更改怎么办?这个电话也需要调整!只传递一个简单的参数需要做很多工作。

现在,如果你仍然想这样做,我建议你看看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; }
}

现在如何使用类型匹配?有两种方法:

  1. 使用Func<string, IFoo>工厂。只需将Func<string, IFoo>注入您想要创建的位置和IFoo
  2. 扩展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);