Подтвердить что ты не робот

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

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

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

vs

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

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

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

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

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

Ответ 6

Вы можете использовать инициализатор объекта для уже созданного экземпляра с помощью следующего трюка. Вы должны создать простую обёртку объекта:

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,
        //...
    }
};

Ответ 7

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