Skip to content

Latest commit

 

History

History
100 lines (71 loc) · 5.31 KB

LDM-2021-03-29.md

File metadata and controls

100 lines (71 loc) · 5.31 KB

C# Language Design Meeting for March 29th, 2021

Agenda

  1. Parameterless struct constructors
  2. AsyncMethodBuilder

Quote of the Day

  • "I told people to question my vague memory and now I'm upset that you did"

Discussion

Parameterless struct constructors

https://github.com/dotnet/csharplang/blob/struct-ctor-more/proposals/csharp-10.0/parameterless-struct-constructors.md

Today, we took a look at open questions in this proposal.

Field initializers and implicit constructors

The main question on our mind here is, what does this code print?

// Is this `default(S)`, or does `Field` have a value of 42?
var s = new S();
System.Console.WriteLine(s.Field);

public struct S {
    public int Field = 42;
    
    public S(int i)
    {
        // C# executes:
        //     Field = 42
    }
}

There are a few possibilities for what this can imply:

  1. There is an implicit parameterless constructor that runs the field initializer. The code sample would print 42.
  2. No implicit parameterless constructors are synthesized. We warn on the field initializer if an explicit parameterless constructor is not provided. The code sample would print 0.
  3. An implicit parameterless constructor is synthesized when no other explicit constructors are provided. The code sample would print 0.

The complication here comes from the fact that new S() was and will continue to be legal, even when there is no parameterless constructor. This differs from class types, which do not have an implicit parameterless constructor that would run such a field initializer. Therefore, one way that we could look at the problem is under the lens of "We've always had this constructor, it's just been omitted as an optimization". By that rule, option 1 should be the tack we take. However, we are also concerned that some users will have taken a dependency on that parameterless constructor meaning a 0-initialized struct.

Another complication was raised in conjunction with record struct primary constructors. In record classes today, we require that if the record has a primary constructor, all other constructors must chain to it. This would mean that the parameterless constructor needs to chain to the primary constructor, which wouldn't work for an implicit parameterless constructor. We could require that record structs with field initializers and a primary constructor provide a parameterless constructor that chains, but that also brings up concerns about nullable annotations of parameters. In C# today, we generally advise struct authors to annotate for typical use, not for all the technical possibilities. That means that if users shouldn't be using a default struct, we advise that a reference field could be annotated not null. That would interact poorly with a requirement to define the primary constructor here, as there could very well be no valid default to specify.

Conclusion

The points raised today around interactions with primary constructors need more thought. We'll take this back for more workshopping, and bring it to LDM again later.

Other conclusions

We also came to a couple other conclusions this session, affirming the proposed behavior of:

  • Definite assignment simplification: the proposed rules are accepted. Further general relaxation of definite assignment might be interesting, but require a separate proposal.
  • Some of the language around how the C# compiler handles private and internal constructors today needs to be modified a bit to reflect more nuances, but we approve of the general goal: keep behavior unchanged.

AsyncMethodBuilder

https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/async-method-builders.md

We started today by questioning whether we can simplify this proposal a bit. Today, the proposal covers both a scoped version and a single-method version. The scoped version is complicated, and has a number of downsides:

  • Diagnostic story is unclear. If the user thought they were applying the builder to all methods with a specific return type but did it incorrectly for a subset of the locations, we have no good way to let them know they made an error.
  • Relatedly, a scoped version would need a good amount of IDE work to help users understand what builder type this method was actually using.

When we look at our use cases for this attribute, we generally believe that there are a couple of main ones. First, and the use case the runtime is interested in, is pooling. This usecase is unlikely to want module-wide application because pooling is potentially harmful for infrequently-used APIs, and so users would want to be more precise about which specific methods in a type are high usage and which are not. The other big use case we can see is builders that responsible for tracing. These builders could potentially be wanted module-wide so that tracing could be added everywhere, but we are again worried about the diagnostic and user-info story for this.

Conclusion

We will remove the scoped version of this proposal for now, and use the existing AsyncMethodBuilder attribute on an individual method, local function, or lambda, to control the builder type for that function. If we hear demand for a module-scoped version in the future, we can use a new attribute designed for that case and think about the diagnostic situation at that point.