TheType obj = new();
while (...)
{
switch (...)
{
case 4223423:
obj.Foo = /* read the value */
break;
}
}
return obj;
This only works for trivial construction; it does not support more complex scenarios:
custom constructors
init-only properties
read-only fields (deferred for now, as this also requires materializer colocation)
A custom constructor (at most one per type) is identified by:
ignore parameterless constructors and constructors that are marked [DapperAot(false)]
if only a single constructor remains, it is selected whether or not it is marked [DapperAot]/[DapperAot(true)]
if multiple constructors remain, and multiple are marked [DapperAot]/[DapperAot(true)], a generator error is emitted and no constructor is selected
if multiple constructors remain, and exactly one is marked [DapperAot]/[DapperAot(true)], it is selected
in all other cases, no constructor is selected
If a custom constructor is selected, the parameters are checked against the member list using normalized / insensitive rules; if any paired members do not match field type (question: nullability?), a generator error is emitted and the constructor is deselected
Init-only properties are any non-static properties that have the init modifier, for example public string Name { get; init; }
In any of these scenarios, we must collect the values into locals, and defer construction; I would propose simply value0, value1, etc where the index is the position of the discovered member; each should be assigned default, awaiting values from the database; we then collect fields into these variables instead of the object:
int value0 = default;
string? value1 = default;
DateTime value2 = default;
while (...)
{
switch (...)
{
case 4223423:
value1 = /* read the value */
break;
}
}
// construction not shown yet
The construction step depends on whether a custom constructor is selected, and whether the parameters for such custom constructor covers all members; there are 3 possible outcomes:
custom constructor covers all members
return new TheType(value0, value2, value1);
(noting that the parameter order does not necessarily match our declaration order, so it is not necessarily strict order)
custom constructor covers some but not all members (which may or may not include init-only properties)
return new TheType(value0, value2)
{
Foo = value1,
};
no custom constructor (and which by elimination: must include at least one init-only property)
return new TheType
{
Foo = value1,
};
Implementation notes:
DRY:
ideally the 3 non-trivial construction techniques (with/without constructor, with/without additional members) should not use 3 separate paths (it is fine to keep the original simple construction TheType obj = new() line separate, for simplicity; see †)
ideally the field read loop should not be duplicated between simple/custom construction; it should just change the target of the read statement between obj.TheMember = vs value42 =
†: the 4th quadrant in that with/without matrix is: simple construction, so: already handled - i.e. without constructor, everything is additional members, none of which are init-only - the difference being that in that simple scenario, we're not using the stack to gather the values - we're pushing them directly onto the object
Right now, the materializer is akin to:
This only works for trivial construction; it does not support more complex scenarios:
A custom constructor (at most one per type) is identified by:
[DapperAot(false)]
[DapperAot]
/[DapperAot(true)]
[DapperAot]
/[DapperAot(true)]
, a generator error is emitted and no constructor is selected[DapperAot]
/[DapperAot(true)]
, it is selectedIf a custom constructor is selected, the parameters are checked against the member list using normalized / insensitive rules; if any paired members do not match field type (question: nullability?), a generator error is emitted and the constructor is deselected
Init-only properties are any non-static properties that have the
init
modifier, for examplepublic string Name { get; init; }
In any of these scenarios, we must collect the values into locals, and defer construction; I would propose simply
value0
,value1
, etc where the index is the position of the discovered member; each should be assigneddefault
, awaiting values from the database; we then collect fields into these variables instead of the object:The construction step depends on whether a custom constructor is selected, and whether the parameters for such custom constructor covers all members; there are 3 possible outcomes:
(noting that the parameter order does not necessarily match our declaration order, so it is not necessarily strict order)
init
-only properties)init
-only property)Implementation notes:
DRY:
TheType obj = new()
line separate, for simplicity; see †)obj.TheMember =
vsvalue42 =
†: the 4th quadrant in that with/without matrix is: simple construction, so: already handled - i.e. without constructor, everything is additional members, none of which are
init
-only - the difference being that in that simple scenario, we're not using the stack to gather the values - we're pushing them directly onto the object