33

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;
Jason Coyne
  • 6,509
  • 8
  • 40
  • 70
  • 1
    Wished C# added some sugar for static "Create" methods like this (like they did for instance "Add" for collections) :) – nawfal Jun 12 '13 at 20:54

7 Answers7

30

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
  • 35,831
  • 5
  • 65
  • 110
  • 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 Mar 24 '09 at 14:20
  • @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 Mar 24 '09 at 15:19
  • @Gaijin posted a third version, along with a comment reminding that its ok to also go with the normal assignments :) – eglasius Mar 24 '09 at 19:39
5

You can use an extension method such as the following:

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

You would call it as follows:

using Utility.Extensions;
// ...
var result = MyClass.FactoryCreate()
                .Initialize(x =>
                {
                    x.someProperty = someValue;
                    x.someProperty2 = someValue2;
                });
Hans Vonn
  • 3,949
  • 3
  • 21
  • 15
5

Yes. You can use object initializer for already created instance with the following trick. You should create a simple object wrapper:

public struct ObjectIniter<TObject>
{
    public ObjectIniter(TObject obj)
    {
        Obj = obj;
    }

    public TObject Obj { get; }
}

And now you can use it like this to initialize your objects:

new ObjectIniter<MyClass>(existingInstance)
{
    Obj =
    {
        //Object initializer of MyClass:
        Property1 = value1,
        Property2 = value2,
        //...
    }
};

P.S. Related discussion in dotnet repository: https://github.com/dotnet/csharplang/issues/803

Roman Artiukhin
  • 2,200
  • 1
  • 9
  • 19
  • Do you even need to specify in the usage? Can't it be inferred from the passed in object? – Jason Coyne Aug 20 '20 at 21:35
  • Unfortunately it's required. The whole trick is based on wrapping existing instance in to constructor. And you can't use type inference in constructors. – Roman Artiukhin Aug 21 '20 at 00:47
2

+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
  • 32,377
  • 16
  • 80
  • 108
  • Nice! Wouldn't it be faster if you do the same with an Expression>, removing the need for refactoring? – configurator Mar 23 '09 at 23:59
  • 2
    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 Mar 24 '09 at 00:18
1

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
  • 86,444
  • 48
  • 176
  • 211
1

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
  • 40,828
  • 14
  • 81
  • 115
0

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

Trap
  • 12,050
  • 15
  • 55
  • 67