É possível usar o Inicializador do Objeto AC# com um método de fábrica?
-
21-08-2019 - |
Pergunta
Eu tenho uma aula com um método estático de fábrica. Quero ligar para a fábrica para recuperar uma instância da classe e, em seguida, faça a inicialização adicional, preferível via sintaxe do inicializador do objeto C#:
MyClass instance = MyClass.FactoryCreate()
{
someProperty = someValue;
}
vs.
MyClass instance = MyClass.FactoryCreate();
instance.someProperty = someValue;
Solução
Não. Alternativamente, você pode aceitar um lambda como um argumento, o que também fornece controle total em qual parte do processo de "criação" será chamada. Dessa forma, você pode chamá -lo como:
MyClass instance = MyClass.FactoryCreate(c=>
{
c.SomeProperty = something;
c.AnotherProperty = somethingElse;
});
A criação seria semelhante a:
public static MyClass FactoryCreate(Action<MyClass> initalizer)
{
MyClass myClass = new MyClass();
//do stuff
initializer( myClass );
//do more stuff
return myClass;
}
Outra opção é retornar um construtor (com um operador de elenco implícito na MyClass). Que você chamaria como:
MyClass instance = MyClass.FactoryCreate()
.WithSomeProperty(something)
.WithAnotherProperty(somethingElse);
Verificar isto para o construtor
Ambas as versões são verificadas no horário de compilação e têm total apoio do Intellisense.
Uma terceira opção que requer um construtor padrão:
//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);
}
}
É um meio entre verificado no horário de compilação e não é verificado. Ele precisa de algum trabalho, pois está forçando a expressão constante nas tarefas. Eu acho que qualquer outra coisa são variações das abordagens já nas respostas. Lembre -se de que você também pode usar as atribuições normais, considere se você realmente precisar disso.
Outras dicas
Você pode usar um método de extensão como o seguinte:
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;
}
}
}
Você chamaria o seguinte:
using Utility.Extensions;
// ...
var result = MyClass.FactoryCreate()
.Initialize(x =>
{
x.someProperty = someValue;
x.someProperty2 = someValue2;
});
+1 em "não".
Aqui está uma alternativa à maneira do objeto anônimo:
var instance = MyClass.FactoryCreate(
SomeProperty => "Some value",
OtherProperty => "Other value");
Nesse caso FactoryCreate()
seria algo como:
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;
}
Não, o inicializador do objeto só pode ser usado em uma chamada para "novo" com o construtor. Uma opção pode ser adicionar alguns args adicionais ao seu método de fábrica, para definir esses valores na criação de objetos dentro da fábrica.
MyClass instance = MyClass.FactoryCreate(int someValue, string otherValue);
Como todos disseram, não.
Um lambda como argumento já foi sugerido.
Uma abordagem mais elegante seria aceitar um anônimo e definir as propriedades de acordo com o objeto. ou seja
MyClass instance = MyClass.FactoryCreate(new {
SomeProperty = someValue,
OtherProperty = otherValue
});
Isso seria muito mais lento, já que o objeto teria que ser refletido para todas as propriedades.
Não, isso é algo que você só pode fazer 'inline'. Toda a função de fábrica pode fazer por você é retornar uma referência.
Para mim, o recurso assassino dos inicializadores de objetos é ver a lista das propriedades não inicializadas restantes. Então, aqui está outro truque de como fazer realmente usar o inicializador de objetos para instância já criada. Você deve criar um invólucro de objeto simples:
public struct ObjectIniter<TObject>
{
public ObjectIniter(TObject obj)
{
Obj = obj;
}
public TObject Obj { get; }
}
E agora você pode usá -lo assim para inicializar seus objetos:
new ObjectIniter<MyClass>(existingInstance)
{
Obj =
{
//Object initializer of MyClass:
Property1 = value1,
Property2 = value2,
//...
}
};