Search code examples
dependency-injectiond

Creating an instance without calling the constructor


I'd like to create an instance, inject a couple of fields and only then call the constructor on that instance.

Example:

class Foo {
    int a = 50;
    int b;

    this() {
        assert(a == 50 && b == 10);
    }
}

...

Foo bar = ???;
bar.b = 10;
bar.this();

Generally, Foo might have multiple overloaded constructors. What's the nicest way to do this, without interfering with the garbage collector and other language mechanisms?


EDIT: Most responses I've gotten so far were along the lines of "why the hell would you want to do that?!"

I'm currently using such @Annotation-driven system for 2 things: automated configuration loading and automated dependency injection. Both have shown to be big productivity gains for me so far and they also work wonders for responsibility decoupling. (see also: Single responsibility principle)

Though perhaps uncommon in D, similar approaches are widely used in other languages such as Java.


Solution

  • I'd say you should change your constructor to accept those arguments.

    But if you really want to, you can break up "bar = new Foo();" into three steps:

    • Allocate the memory with the appropriate size (__traits(classInstanceSize)
    • Initialize the memory with the appropriate contents (typeid().init - this holds values for like your a=50, the pointer to the virtual table, etc.)
    • Call the constructor

    And, of course, returning the new reference.

    import core.memory; // for GC.malloc
    enum size = __traits(classInstanceSize, Foo); // get the size
    auto memory = GC.malloc(size)[0 .. size]; // alloc mem and slice it for bounds checking
    memory[] = typeid(Foo).init[]; // initialize the memory
    
    Foo foo = cast(Foo) memory.ptr; // we can now cast it to the reference
    
    foo.b = 10; // here's your special line
    
    foo.__ctor(); // and now call the constructor. Can pass any arguments here, D will handle the overloads normally
    
    assert(foo.a == 50); // ensure everything worked
    assert(foo.b == 10);
    assert(typeid(foo) == typeid(Foo));
    
    // BTW don't use the variable memory anymore - only use the typed Foo instance
    

    Breaking down the steps like this also lets you replace the allocation method, if you want. Phobos' std.conv.emplace function performs steps 2 and 3 in a single function, to make that easier with custom allocation.

    But since you want to insert code between steps 2 and 3, you gotta do it yourself.