- "Where did you think we put that? Magic?"
#2691
https://github.com/dotnet/csharplang/blob/2867220fe142dfefd47d8cb20fb88393e4156059/proposals/primary-constructors.md#open-questions
We first considered the question of what happens when a lambda in a field or property initializer captures a parameter that is also captured by the containing type. For example:
partial class C1
{
public System.Func<int> F1 = Execute1(() => p1++); // Capture 1
}
partial class C1 (int p1)
{
public int M1() { return p1++; } // Capture 2
static System.Func<int> Execute1(System.Func<int> f)
{
_ = f();
return f;
}
}
To follow standard capture rules, both p1
s will need to refer to the same storage location, and observe the changes from one to the other. There's really two questions here:
first, should we allow this at all? And second, how will we implement it (and do any implementation strategies have semantic implications)?
For the first question, we started by thinking about whether to disallow the scenario where a primary constructor parameter is captured by two locations at once. But we're concerned
about spooky action at a distance here, where adding a field initializer can cause compilation errors in a different location. We also don't think that the issue that the rule was
originally created to avoid is an issue in this area: this
is not being generally exposed before the base constructor has been run, and there are no changes to virtual members that
can be invoked before the base constructor has run. We therefore lean towards allowing it.
For the second question, we thought about whether to do a double-closure technique, where we create an inner closure type that is then shared between the accesses, or whether to simply
close over this
in the lambda. After some consideration, we think we're ok with just using this
, particularly as the double-capture could potentially have some bleed-through effects
on whether an individual type is considered unmanaged
, and potentially causes allocations that weren't expected. The main issue will be working with the runtime team to ensure that
ILVerify understands the pattern.
This scenario is allowed, and we will work with the runtime team to make sure that ILVerify is updated for this scenario.
Finally, we looked at whether allowing self-assignment in a struct to silently overwrite primary constructor parameter captures is a good thing, or if we should warn/error/something else
for the scenario. We think that this is the least surprising behavior that this could have, and that it would be more surprising if it didn't have this effect. A warning does not seem
appropriate: either you didn't even know this syntax was possible, or you do know what this syntax means and you were expecting the state of this
to be overwritten; primary constructor
parameters are part of that state, so they should logically be overwritten.
Allowed, no warning.