-
-
Notifications
You must be signed in to change notification settings - Fork 35
Description
Right now, the materializer is akin to:
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 yetThe 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 =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