2009-03-23 16 views
24

Tengo una clase con un método de fábrica estático en ella. Quiero llamar a la fábrica para recuperar una instancia de la clase, y luego hacer la inicialización adicional, preferablly a través de C# inicializador de objeto de sintaxis:¿Es posible utilizar un inicializador de objetos C# con un método de fábrica?

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

vs

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

Deseado C# agrega un poco de azúcar para la estática "Crear" métodos como éste (como lo hicieron, por ejemplo, en "Añadir" para las colecciones) :) – nawfal

Respuesta

25

No. Como alternativa, podría aceptar un lambda como argumento, que también le brinda control total sobre la parte del proceso de "creación" que se llamará. De esta manera se le puede llamar como:

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

La creación se vería similar a:

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

Otra opción es volver a un constructor en su lugar (con un operador de conversión implícita a MiClase). Que podríamos llamar como:

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

Comprobar this para el constructor

Ambas versiones se comprueban en tiempo de compilación y tienen compatibilidad con IntelliSense completa.


Una tercera opción que requiere un constructor por defecto:

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

Es un medio entre verificado en tiempo de compilación y no comprobadas. Necesita algo de trabajo, ya que está forzando la expresión constante de las asignaciones. Creo que cualquier otra cosa son variaciones de los enfoques que ya están en las respuestas. Recuerde que también puede usar las asignaciones normales, considere si realmente necesita algo de esto.

+0

me gusta la solución lambda, que es algo cerca de la sintaxis correcta, pero el la sintaxis del constructor requiere que realice una función para cada propiedad, lo que no es viable, especialmente en una situación de fábrica abstracta. –

+0

@Gaijin Estoy de acuerdo, la lambda es una manera muy rápida, agradable y bien respaldada. Utilizo el constructor para el código de prueba con valores predeterminados claros y algunos métodos que no son solo para establecer una propiedad. En particular, es muy útil si MyClass es inmutable (ya que necesita aplicarlo todo en el constructor). – eglasius

+0

@Gaijin publicó una tercera versión, junto con un comentario que recuerda que está bien también ir con las asignaciones normales :) – eglasius

1

No, el inicializador de objeto sólo puede ser utilizado en una llamada a "nuevo" con el constructor. Una opción podría ser agregar algunos argumentos adicionales a su método de fábrica, para establecer esos valores en la creación de objetos dentro de la fábrica.

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

No, eso es algo que solo puedes hacer 'en línea'. Todo lo que la función de fábrica puede hacer por usted es devolver una referencia.

1

Como todos dijeron, no.

Ya se ha sugerido una lambda como argumento.
Un enfoque más elegante sería aceptar un anónimo y establecer las propiedades de acuerdo con el objeto. es decir

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

Eso sería mucho más lento, ya que el objeto debería reflejarse en todas las propiedades.

2

+1 en "No".

Aquí es una alternativa a la forma en objeto anónimo:

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

En este caso FactoryCreate() sería algo así 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; 
} 
+0

¡Agradable! ¿No sería más rápido si haces lo mismo con un objeto >, eliminando la necesidad de refactorizar? – configurator

+2

Para responderme a mí mismo: No, no lo haría. Nos haría compilar() la expresión todo el tiempo. Benchmark dice que es más de 30 veces más lento ... – configurator

3

Se puede utilizar un método de extensión tales como los siguientes:

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

Lo llamaría de la siguiente manera:

using Utility.Extensions; 
// ... 
var result = MyClass.FactoryCreate() 
       .Initialize(x => 
       { 
        x.someProperty = someValue; 
        x.someProperty2 = someValue2; 
       }); 
Cuestiones relacionadas