GenerateThis creates a constructor with a parameter for every field.
class Class { int field; mixin(GenerateThis); } auto obj = new Class(5); obj.field.shouldEqual(5);
When the super class also has a generated constructor, it will be called first.
class Class { int field; mixin(GenerateThis); } class Child : Class { int field2; mixin(GenerateThis); } auto obj = new Child(5, 8); obj.field.shouldEqual(5); obj.field2.shouldEqual(8);
Methods are ignored when generating constructors.
class Class { int field; void method() { } mixin(GenerateThis); } auto obj = new Class(5); obj.field.shouldEqual(5);
When passing arrays to the constructor, these arrays are automatically dup-ed.
class Class { int[] array; mixin(GenerateThis); } auto array = [2, 3, 4]; auto obj = new Class(array); array[0] = 1; obj.array[0].shouldEqual(2);
Arrays passed to the constructor are dup-ed even if they're inside a Nullable.
import std.typecons : Nullable, nullable; class Class { Nullable!(int[]) array; mixin(GenerateThis); } auto array = [2, 3, 4]; auto obj = new Class(array.nullable); array[0] = 1; obj.array.get[0].shouldEqual(2); obj = new Class(Nullable!(int[]).init); obj.array.isNull.shouldBeTrue;
Associative arrays are also dup-ed.
class Class { int[int] array; mixin(GenerateThis); } auto array = [2: 3]; auto obj = new Class(array); array[2] = 4; obj.array.shouldEqual([2: 3]);
@(This.Default!value) defines a default value for the constructor parameter.
class Class { @(This.Default!5) int value = 5; mixin(GenerateThis); } auto obj1 = new Class(); obj1.value.shouldEqual(5); auto obj2 = new Class(6); obj2.value.shouldEqual(6);
When using GenerateThis in an empty struct, no constructor is created.
This is because D does not allow empty constructor methods.
struct Struct { mixin(GenerateThis); } auto strct = Struct();
@(This.Default!(lambda)) calls the lambda to generate the default value.
This is to handle cases like @(This.Default!(new Class)), where D would allocate the class during startup and reuse the same reference for every constructor call.
import std.conv : to; class Class { @(This.Default!(() => new Object)) Object obj; mixin(GenerateThis); } auto obj1 = new Class(); auto obj2 = new Class(); (cast(void*) obj1.obj).shouldNotEqual(cast(void*) obj2.obj);
When the superclass has a generated constructor, the order of parameters is:
- Super class fields - Class fields - Class fields with default value - Super class fields with default value
class Parent { int field1; @(This.Default!2) int field2 = 2; mixin(GenerateThis); } class Child : Parent { int field3; @(This.Default!4) int field4 = 4; mixin(GenerateThis); } auto obj = new Child(1, 2, 3, 4); obj.field1.shouldEqual(1); obj.field3.shouldEqual(2); obj.field4.shouldEqual(3); obj.field2.shouldEqual(4);
No constructor parameter is generated for static fields.
class Class { static int field1; int field2; mixin(GenerateThis); } auto obj = new Class(5); obj.field1.shouldEqual(0); obj.field2.shouldEqual(5);
Immutable arrays are supported as constructor parameters.
class Class { immutable(Object)[] array; mixin(GenerateThis); }
@(This.Private/Protected/Public) can be used to define the visibility scope of the constructor.
@(This.Private) class PrivateClass { mixin(GenerateThis); } @(This.Protected) class ProtectedClass { mixin(GenerateThis); } @(This.Package) class PackageClass { mixin(GenerateThis); } @(This.Package("boilerplate")) class SubPackageClass { mixin(GenerateThis); } class PublicClass { mixin(GenerateThis); } static assert(__traits(getProtection, PrivateClass.__ctor) == "private"); static assert(__traits(getProtection, ProtectedClass.__ctor) == "protected"); static assert(__traits(getProtection, PackageClass.__ctor) == "package"); // getProtection does not return the package name of a package() attribute // static assert(__traits(getProtection, SubPackageClass.__ctor) == `package(boilerplate)`); static assert(__traits(getProtection, PublicClass.__ctor) == "public");
@(This.Private/Protected/Public) also assigns a visibility scope to the generated builder.
@(This.Private) class PrivateClass { mixin(GenerateThis); } @(This.Protected) class ProtectedClass { mixin(GenerateThis); } @(This.Package) class PackageClass { mixin(GenerateThis); } @(This.Package("boilerplate")) class SubPackageClass { mixin(GenerateThis); } class PublicClass { mixin(GenerateThis); } static assert(__traits(getProtection, PrivateClass.Builder) == "private"); static assert(__traits(getProtection, ProtectedClass.Builder) == "protected"); static assert(__traits(getProtection, PackageClass.Builder) == "package"); static assert(__traits(getProtection, PublicClass.Builder) == "public");
@(This.Default) without a parameter uses the default value of the type.
class Class { @(This.Default) string s; @(This.Default) int i; mixin(GenerateThis); } (new Class()).i.shouldEqual(0); (new Class()).s.shouldEqual(string.init);
@(This.Exclude) excludes a field from being set by the generated constructor.
class Class { @(This.Exclude) int i = 5; mixin(GenerateThis); } (new Class).i.shouldEqual(5);
Even if the class holds a field that is not const, a const array can be passed to it as long as the dup of this field does not leak mutable references.
struct Struct { } class Class { Struct[] values_; mixin(GenerateThis); } const Struct[] constValues; auto obj = new Class(constValues);
Property functions are disregarded by the generated constructor
class Class { int a; @property int foo() const { return 0; } mixin(GenerateThis); } static assert(__traits(compiles, new Class(0))); static assert(!__traits(compiles, new Class(0, 0)));
When no parameters need to be dupped, the generated constructor is @nogc.
struct Struct { int a; mixin(GenerateThis); } auto str = Struct(5);
@(This.Init!value) defines a value for the field that will be assigned in the constructor, and excludes the field from being a constructor parameter.
class Class { @(This.Init!5) int field1; @(This.Init!(() => 8)) int field2; mixin(GenerateThis); } auto obj = new Class; obj.field1.shouldEqual(5); obj.field2.shouldEqual(8);
@(This.Init!lambda), like @(This.Default!lambda), will be called automatically with this to determine the initializer value.
class Class { int field1; @(This.Init!(self => self.field1 + 5)) int field2; mixin(GenerateThis); } auto obj = new Class(5); obj.field1.shouldEqual(5); obj.field2.shouldEqual(10);
@(This.Init!lambda) can allocate runtime values.
class Class1 { @(This.Init!(self => new Object)) Object object; mixin(GenerateThis); } class Class2 { @(This.Init!(() => new Object)) Object object; mixin(GenerateThis); } class Class3 : Class2 { mixin(GenerateThis); }
GenerateThis creates a .Builder() property that can be used to incrementally construct the value.
builder.value will call the generated constructor with the assigned values.
static class Class { int field1; int field2; int field3; mixin(GenerateThis); } auto obj = { with (Class.Builder()) { field1 = 1; field2 = 2; field3 = 3; return value; } }(); with (obj) { field1.shouldEqual(1); field2.shouldEqual(2); field3.shouldEqual(3); }
The order in which Builder fields are set is irrelevant.
static class Class { int field1; int field2; int field3; mixin(GenerateThis); } auto obj = { with (Class.Builder()) { field3 = 1; field1 = 2; field2 = 3; return value; } }(); with (obj) { field1.shouldEqual(2); field2.shouldEqual(3); field3.shouldEqual(1); }
Builder fields with a @(This.Default) value can be omitted.
static class Class { int field1; @(This.Default!5) int field2; int field3; mixin(GenerateThis); } // constructor is this(field1, field3, field2 = 5) auto obj = { with (Class.Builder()) { field1 = 1; field3 = 3; return value; } }(); with (obj) { field1.shouldEqual(1); field2.shouldEqual(5); field3.shouldEqual(3); }
Builder can be used with structs as well as classes.
struct Struct { int field1; int field2; int field3; mixin(GenerateThis); } auto value = { with (Struct.Builder()) { field1 = 1; field3 = 3; field2 = 5; return value; } }(); static assert(is(typeof(value) == Struct)); with (value) { field1.shouldEqual(1); field2.shouldEqual(5); field3.shouldEqual(3); }
Builder fields don't contain the trailing newline of a private field.
struct Struct { private int a_; mixin(GenerateThis); } auto builder = Struct.Builder(); builder.a = 1; auto value = builder.value; value.shouldEqual(Struct(1));
When a field in the type has a generated constructor itself, its fields can be set directly on a Builder of the outer type.
struct Struct1 { int a; int b; mixin(GenerateThis); } struct Struct2 { int c; Struct1 struct1; int d; mixin(GenerateThis); } auto builder = Struct2.Builder(); builder.struct1.a = 1; builder.struct1.b = 2; builder.c = 3; builder.d = 4; auto value = builder.value; static assert(is(typeof(value) == Struct2)); with (value) { struct1.a.shouldEqual(1); struct1.b.shouldEqual(2); c.shouldEqual(3); d.shouldEqual(4); }
Builder can use @(This.Default) values for nested fields.
struct Struct1 { int a; int b; mixin(GenerateThis); } struct Struct2 { int c; @(This.Default!(Struct1(3, 4))) Struct1 struct1; int d; mixin(GenerateThis); } auto builder = Struct2.Builder(); builder.c = 1; builder.d = 2; builder.value.shouldEqual(Struct2(1, 2, Struct1(3, 4)));
Builder also allows assigning a single value directly for a nested Builder field.
struct Struct1 { int a; int b; mixin(GenerateThis); } struct Struct2 { int c; Struct1 struct1; int d; mixin(GenerateThis); } auto builder = Struct2.Builder(); builder.struct1 = Struct1(2, 3); builder.c = 1; builder.d = 4; builder.value.shouldEqual(Struct2(1, Struct1(2, 3), 4));
When a Builder field is an array, it can be initialized by specifying the value for each desired index.
struct Struct1 { int value; mixin(GenerateThis); } struct Struct2 { Struct1[] array; mixin(GenerateThis); } auto builder = Struct2.Builder(); builder.array[0].value = 1; builder.array[1].value = 2; builder.value.shouldEqual(Struct2([Struct1(1), Struct1(2)]));
Builder handles fields that are aliased to this.
struct Struct2 { string text; alias text this; mixin(GenerateThis); } struct Struct1 { Struct2 nested; mixin(GenerateThis); } auto builder = Struct1.Builder(); builder.nested.text = "foo"; builder.value.shouldEqual(Struct1(Struct2("foo")));
When a Builder field is an array, it can also be initialized by appending values.
struct Struct1 { int value; mixin(GenerateThis); } struct Struct2 { Struct1[] array; mixin(GenerateThis); } auto builder = Struct2.Builder(); builder.array ~= Struct1(1); builder.array ~= Struct1(2); builder.value.shouldEqual(Struct2([Struct1(1), Struct1(2)]));
When a value has been assigned to a recursive Builder field, its fields can still be individually overridden. This uses the BuilderFrom reverse-Builder property.
struct Struct1 { int a; int b; mixin(GenerateThis); } struct Struct2 { Struct1 struct1; mixin(GenerateThis); } auto builder = Struct2.Builder(); builder.struct1 = Struct1(2, 3); builder.struct1.b = 4; builder.value.shouldEqual(Struct2(Struct1(2, 4)));
When a recursive Builder field has already been directly assigned, it cannot be later overwritten with a whole-value assignment.
import core.exception : AssertError; struct Struct1 { int a; int b; mixin(GenerateThis); } struct Struct2 { Struct1 struct1; mixin(GenerateThis); } auto builder = Struct2.Builder(); builder.struct1.b = 4; void set() { builder.struct1 = Struct1(2, 3); } set().shouldThrow!AssertError("Builder: cannot set field by value since a subfield has already been set.");
Builder supports assigning const fields.
struct Struct { const int a; mixin(GenerateThis); } with (Struct.Builder()) { a = 5; value.shouldEqual(Struct(5)); }
Builder supports assigning recursive values with a destructor.
static struct Struct1 { ~this() pure @safe @nogc nothrow { } } struct Struct2 { Struct1 struct1; mixin(GenerateThis); } with (Struct2.Builder()) { struct1 = Struct1(); value.shouldEqual(Struct2(Struct1())); }
When a Builder field is Nullable!T, it can be directly assigned a T.
import std.typecons : Nullable, nullable; struct Struct { const Nullable!int a; mixin(GenerateThis); } with (Struct.Builder()) { a = 5; value.shouldEqual(Struct(5.nullable)); }
When a Builder field is Nullable!T, and T has a Builder, T's fields can be directly assigned.
import std.typecons : Nullable, nullable; struct Struct1 { int a; mixin(GenerateThis); } struct Struct2 { @(This.Default) Nullable!Struct1 b; mixin(GenerateThis); } with (Struct2.Builder()) { value.shouldEqual(Struct2(Nullable!Struct1())); b.a = 5; value.shouldEqual(Struct2(Nullable!Struct1(Struct1(5)))); }
When a Builder field is Nullable!T, and T has a Builder, T can still be assigned either as T or Nullable!T.
import std.typecons : Nullable, nullable; struct Struct1 { mixin(GenerateThis); } struct Struct2 { @(This.Default) Nullable!Struct1 value; mixin(GenerateThis); } with (Struct2.Builder()) { builderValue.shouldEqual(Struct2()); value = Struct1(); builderValue.shouldEqual(Struct2(Struct1().nullable)); } with (Struct2.Builder()) { value = Nullable!Struct1(); builderValue.shouldEqual(Struct2()); }
A value with GenerateThis can be turned back into a builder using BuilderFrom(). This can be used to reassign immutable fields.
import std.typecons : Nullable, nullable; struct Struct { private int a_; int[] b; mixin(GenerateThis); } const originalValue = Struct(2, [3]); with (originalValue.BuilderFrom()) { a = 5; value.shouldEqual(Struct(5, [3])); }
When a type already has a value field, builderValue can be used to get the builder value.
import std.typecons : Nullable, nullable; struct Struct { private int value_; mixin(GenerateThis); } with (Struct.Builder()) { value = 5; builderValue.shouldEqual(Struct(5)); }
Builder will handle structs that contain structs with @disable(this).
import std.typecons : Nullable, nullable; static struct Inner { private int i_; @disable this(); mixin(GenerateThis); } static struct Struct { private Inner inner_; mixin(GenerateThis); } with (Struct.Builder()) { inner.i = 3; value.shouldEqual(Struct(Inner(3))); }
GenerateThis is a mixin string that automatically generates a this() function, customizable with UDA.