Можно ли использовать инициализатор объекта С# с фабричным методом?

StackOverflow https://stackoverflow.com/questions/675545

Вопрос

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

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

против

MyClass instance = MyClass.FactoryCreate();
instance.someProperty = someValue;
Это было полезно?

Решение

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

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

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

Другие советы

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

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

+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;
}

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

MyClass instance = MyClass.FactoryCreate(int someValue, string otherValue);

Как все сказали, нет.

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

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

Однако это будет намного медленнее, поскольку объекту придется отразить все свойства.

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

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

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,
        //...
    }
};
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top