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

时间:2014-10-09 16:44:58

标签: c# lambda ninject

我对实现lambdas和表达式并不是很熟悉,但我在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的实例。我知道这是service locator anti pattern,但由于其他限制,我们在这种情况下别无选择:

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);

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

3 个答案:

答案 0 :(得分:2)

从概念上讲,缺乏工厂(工厂界面),因此应该引入它以避免直接使用容器。

Ninject Factory(工厂接口)扩展可用于创建实例,如下所示:

声明工厂界面:

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/ninject.extensions.factory了解替代方案。

答案 1 :(得分:2)

构造函数参数名称和表达式的问题是,当表达式覆盖构造函数的所有参数时,表达式才有效/完整。现在我想你想要注入一些参数(让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");

因此,您还必须指定IServiceOneIServiceTwo,而不是仅指定您感兴趣的“staticArgument”。如果构造函数改变了怎么办?那么电话也需要调整!很多工作只是传递一个简单的参数。

现在,如果你仍然想要这样做,我建议您查看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/),以便您可以执行以下操作:
  3. public interface IFooFactory
    {
        IFoo Create([MatchByType]string someParam, string matchByName);
    }
    

    (==&gt;使用属性告诉工厂扩展如何将参数传递给Get<IFoo>请求。)

答案 2 :(得分:0)

看下面的文章 - 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;
    }
}

注意 - 此方法可能无法用于所有可能的方式,其中可以使用表达式来指示类的属性名称,因此可能需要根据您的需要进行增强(您需要它的通用性)。

但基本上,一旦你有了这个帮助方法,你的通话就会变成

var settings = new Settings();
Ioc.Instance.Get<Class>(GetPropertyName(x => x.settings), settings);