Можно ли использовать инициализатор объекта С# с фабричным методом?
-
21-08-2019 - |
Вопрос
У меня есть класс со статическим фабричным методом.Я хочу вызвать фабрику, чтобы получить экземпляр класса, а затем выполнить дополнительную инициализацию, предпочтительно через синтаксис инициализатора объекта С#:
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,
//...
}
};