Skip to content

Latest commit

 

History

History
68 lines (47 loc) · 3.43 KB

LDM-2023-02-15.md

File metadata and controls

68 lines (47 loc) · 3.43 KB

C# Language Design Meeting for February 15th, 2023

Agenda

Quote of the Day

  • "Where did you think we put that? Magic?"

Discussions

Open questions in primary constructors

#2691
https://github.com/dotnet/csharplang/blob/2867220fe142dfefd47d8cb20fb88393e4156059/proposals/primary-constructors.md#open-questions

Capturing parameters in lambdas

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 p1s 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.

Conclusion

This scenario is allowed, and we will work with the runtime team to make sure that ILVerify is updated for this scenario.

Assigning to this in a struct

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.

Conclusion

Allowed, no warning.