2014-10-09 140 views
3

我不是太熟悉,实施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一个实例。我知道这是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); 

所以,这是可能的,代码是什么样子?

回答

2

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

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

声明一个工厂接口:

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的替代方案。

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

这是否构造函数?在我的测试中,我只能看到课堂上的属性? – 2014-10-10 09:36:42

2

与构造函数的参数名称和表现的问题是,一个表达式是唯一有效/完全在它覆盖的构造函数的所有参数。现在,我想你要注入一些参数(有ninject处理它们),以及您想传递一个值的一个或两个特定的参数,比方说,它看起来像:

public interface IFoo { } 
public class Foo : IFoo 
{ 
    public Foo(IServiceOne one, IServiceTwo two, string parameter) {...} 
} 

Ninject支持构造函数表达式,但只有绑定和他们的工作是这样的:

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布鲁诺夫的答案和使用Ninject.Extensions.Factory。现在我想你会说这是不好的,因为你仍然必须指定参数名称,..这不是重构安全和麻烦(没有代码完成...)。

但是,有问题的解决方案:不使用“匹配”参数名称的构造函数参数,您可以使用类型匹配参数。好的,有一个问题。如果你有多个相同类型的参数,那么它将无法工作。但我认为这是罕的情况下,你仍然可以引入一个容器数据级来解决它:

public class FooArguments 
{ 
    string Argument1 { get; set; } 
    string Argument2 { get; set; } 
} 

现在你怎么能使用类型匹配? 有两种方法:

  1. 使用Func<string, IFoo>工厂。只需将Func<string, IFoo>注入到您想要创建的位置,然后IFoo即可。
  2. 延长工厂扩展。是的,你听说过对;-)其实并不困难。你“只是”需要实现自定义IInstanceProvider(见http://www.planetgeek.ch/2011/12/31/ninject-extensions-factory-introduction/),这样你就可以是这样的:
public interface IFooFactory 
{ 
    IFoo Create([MatchByType]string someParam, string matchByName); 
} 

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

+1

再次感谢您提供非常全面的答案。我使用Ninject工厂选项。尽管如果'setting' arg被重命名,它会中断,至少魔术字符串已经消失。 – 2014-10-10 14:27:34