Можно ли использовать инициализатор объекта c # с фабричным методом? - PullRequest
26 голосов
/ 24 марта 2009

У меня есть класс со статическим фабричным методом. Я хочу вызвать фабрику для получения экземпляра класса, а затем выполнить дополнительную инициализацию, предпочтительно через синтаксис инициализатора объекта c #:

MyClass instance = MyClass.FactoryCreate()
{
  someProperty = someValue;
}

против

MyClass instance = MyClass.FactoryCreate();
instance.someProperty = someValue;

Ответы [ 7 ]

27 голосов
/ 24 марта 2009

Нет. В качестве альтернативы вы можете принять лямбду в качестве аргумента, который также дает вам полный контроль над тем, какая часть процесса «создания» будет вызвана. Таким образом, вы можете назвать это как:

MyClass instance = MyClass.FactoryCreate(c=>
   {
       c.SomeProperty = something;
       c.AnotherProperty = somethingElse;
   });

Создание будет выглядеть примерно так:

public static MyClass FactoryCreate(Action<MyClass> initalizer)
{
    MyClass myClass = new MyClass();
    //do stuff
    initializer( myClass );
    //do more stuff
    return myClass;
}

Другой вариант - вместо этого вернуть построитель (с неявным оператором приведения к MyClass). Который вы бы назвали, как:

MyClass instance = MyClass.FactoryCreate()
   .WithSomeProperty(something)
   .WithAnotherProperty(somethingElse);

Проверьте это для строителя

Обе эти версии проверяются во время компиляции и имеют полную поддержку intellisense.


Третий вариант, для которого требуется конструктор по умолчанию:

//used like:
var data = MyClass.FactoryCreate(() => new Data
{
    Desc = "something",
    Id = 1
});
//Implemented as:
public static MyClass FactoryCreate(Expression<Func<MyClass>> initializer)
{
    var myclass = new MyClass();
    ApplyInitializer(myclass, (MemberInitExpression)initializer.Body);
    return myclass ;
}
//using this:
static void ApplyInitializer(object instance, MemberInitExpression initalizer)
{
    foreach (var bind in initalizer.Bindings.Cast<MemberAssignment>())
    {
        var prop = (PropertyInfo)bind.Member;
        var value = ((ConstantExpression)bind.Expression).Value;
        prop.SetValue(instance, value, null);
    }
}

Это среднее между проверенным во время компиляции и не проверенным. Это требует некоторой работы, так как оно вызывает постоянное выражение в присваиваниях. Я думаю, что все остальное - вариации подходов, которые уже есть в ответах. Помните, что вы также можете использовать обычные задания, подумайте, действительно ли вам это нужно.

3 голосов
/ 17 апреля 2017

Вы можете использовать метод расширения, такой как следующий:

namespace Utility.Extensions
{
    public static class Generic
    {
        /// <summary>
        /// Initialize instance.
        /// </summary>
        public static T Initialize<T>(this T instance, Action<T> initializer)
        {
            initializer(instance);
            return instance;
        }
    }
}

Вы бы назвали это следующим образом:

using Utility.Extensions;
// ...
var result = MyClass.FactoryCreate()
                .Initialize(x =>
                {
                    x.someProperty = someValue;
                    x.someProperty2 = someValue2;
                });
2 голосов
/ 24 марта 2009

+ 1 на «Нет».

Вот альтернатива анонимному объекту:

var instance = MyClass.FactoryCreate(
    SomeProperty => "Some value",
    OtherProperty => "Other value");

В этом случае FactoryCreate() будет выглядеть примерно так:

public static MyClass FactoryCreate(params Func<object, object>[] initializers)
{
    var result = new MyClass();
    foreach (var init in initializers) 
    {
        var name = init.Method.GetParameters()[0].Name;
        var value = init(null);
        typeof(MyClass)
            .GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase)
            .SetValue(result, value, null);
    }
    return result;
}
1 голос
/ 24 марта 2009

Как все говорили, нет.

Лямбда в качестве аргумента уже предложена.
Более элегантный подход заключается в том, чтобы принять аноним и установить свойства в соответствии с объектом. т.е.

MyClass instance = MyClass.FactoryCreate(new {
    SomeProperty = someValue,
    OtherProperty = otherValue
});

Хотя это будет намного медленнее, поскольку объект должен отражаться для всех свойств.

1 голос
/ 24 марта 2009

Нет, инициализатор объекта можно использовать только при вызове «new» с конструктором. Одним из вариантов может быть добавление некоторых дополнительных аргументов в метод фабрики, чтобы установить эти значения при создании объекта внутри фабрики.

MyClass instance = MyClass.FactoryCreate(int someValue, string otherValue);
0 голосов
/ 10 марта 2019

Для меня функция убийцы для инициализаторов объектов - видеть список оставшихся неинициализированных свойств. Итак, вот еще один трюк, как заставить фактически использовать инициализатор объекта для уже созданного экземпляра. Вы должны создать простую обёртку объекта:

public struct ObjectIniter<TObject>
{
    public ObjectIniter(TObject obj)
    {
        Obj = obj;
    }

    public TObject Obj { get; }
}

И теперь вы можете использовать его для инициализации ваших объектов:

new ObjectIniter<MyClass>(existingInstance)
{
    Obj =
    {
        //Object initializer of MyClass:
        Property1 = value1,
        Property2 = value2,
        //...
    }
};
0 голосов
/ 24 марта 2009

Нет, это то, что вы можете делать только «в строке». Все, что заводская функция может сделать для вас, это вернуть ссылку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...