2014-10-09 3 views
3

Я не слишком знаком с реализацией лямбды и выражений, но я привык к этому синтаксису много раз в MVC, где лямбда идентифицирующих свойств на объекте:Как использовать лямбда для указания аргумента конструктора?

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

Я хотел бы, чтобы реорганизовать его выглядеть так, что она сильно типизированных, используя лямбда для указания какой аргумент в отношении конструктора, который я поставляю:

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

Итак, возможно ли это, и как будет выглядеть код?

ответ

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.

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

Это работает конструктор? В моем тесте я могу видеть только свойства класса? –

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

поэтому вместо того, только с указанием «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; } 
} 

Теперь, как вы можете использовать согласование типа? Там два способа:

  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, по крайней мере, магические строки исчезли. –

Смежные вопросы