views:

300

answers:

5

I have a class with a static factory method on it. I want to call the factory to retrieve an instance of the class, and then do additional initialization, preferablly via c# object initializer syntax :

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

vs

MyClass instance = MyClass.FactoryCreate();
instance.someProperty = someValue;
+1  A: 

No, the object initializer can only be used on a call to "new" with the constructor. One option might be to add some additional args to your factory method, to set those values at object creation inside the factory.

MyClass instance = MyClass.FactoryCreate(int someValue, string otherValue);
Andy White
A: 

No, that's something you can only do 'inline'. All the factory function can do for you is to return a reference.

Trap
+7  A: 

No. Alternatively you could accept a lambda as an argument, which also gives you full control in which part of the "creation" process will be called. This way you can call it like:

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

The create would look similar to:

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


Another option is to return a builder instead (with an implicit cast operator to MyClass). Which you would call like:

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

Check this for the builder

Both of these versions are checked at compile time and have full intellisense support.


A third option that requires a default constructor:

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

Its a middle between checked at compile time and not checked. It does need some work, as it is forcing constant expression on the assignments. I think that anything else are variations of the approaches already in the answers. Remember that you can also use the normal assignments, consider if you really need any of this.

eglasius
I like the lambda solution, which is somewhat close to the right syntax, but the builder syntax requires me to make a function for every property, which is not viable, especially in an abstract factory situation.
Jason Coyne
@Gaijin I agree, the lambda is a really quick, nice and well supported way. I use the builder for test code with clear defaults and some methods that are not to just set a property. In particular it is really useful if MyClass is immutable (as you need to apply it all at the constructor).
eglasius
@Gaijin posted a third version, along with a comment reminding that its ok to also go with the normal assignments :)
eglasius
+1  A: 

Like everyone said, no.

A lambda as an argument has already been suggested.
A more elegant approach would be to accept an anonymous and set the properties according to the object. i.e.

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

That would be much slower though, since the object would have to be reflected on for all the properties.

configurator
+2  A: 

+1 on "No".

Here's an alternative to the anonymous object way:

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

In this case FactoryCreate() would be something like:

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;
}
Paul Stovell
Nice! Wouldn't it be faster if you do the same with an Expression<Func<object, object>>, removing the need for refactoring?
configurator
To answer myself: No, it wouldn't. It would cause us to Compile() the expression every time. Benchmark says it's over 30 times slower...
configurator