Skip to content

Latest commit

 

History

History
77 lines (58 loc) · 3.35 KB

LDM-2023-05-08.md

File metadata and controls

77 lines (58 loc) · 3.35 KB

C# Language Design Meeting for May 8th, 2023

Agenda

Quote of the Day

  • "Depending on how you interpret it, the results can be whatever you want"

Discussion

Primary Constructors

#2691
#7109 (reply in thread)

Today we looked at one of the pieces of community feedback for primary constructors, around name shadowing from base types. A user might not expect the output that this program will give:

public class Base
{
    protected readonly string a = "base";
}

public class Derived(string a = "derived") : Base
{
    public void M()
    {
        Console.WriteLine(a);
    }
}

This code will print base when Derived.M() is called, because the base field shadows the primary constructor parameter. While this could be confusing for this particular case, we don't think this is common case for this type of code; instead, the common case is something more like the following:

public class Base(string a)
{
    protected readonly string a = a;
}

public class Derived(string a = "derived") : Base(a)
{
    public void M()
    {
        Console.WriteLine(a);
    }
}

In this example, a is passed through to Base via its constructor, and the protected field is referenced by the derived type ensuring that the value is not double-stored. There are a few possible options we can look for improving the first scenario:

  1. Warn whenever a base member shadows a primary constructor parameter at the use site. This would produce a warning in both of the above code samples.
  2. A variation on 1, produce a warning whenever a base member shadows a primary constructor parameter and that parameter wasn't passed to the base type. There are 2 subvariants of this:
    1. Ensure that names match when doing this. IE, passing a to a parameter named b would not count for the purposes of suppressing the warning.
    2. Do no validation on the name of the parameter.
  3. Change the shadowing order: make the primary ctor parameter shadow the base member, so accessing the base member would require base. qualification.

For 1, we're concerned about the impact to the second code example: that seems the far more common case in the wild, and impacting it negatively isn't great. For 3, we're unsure whether the effective behavior being different than records will be confusing: Derived would not create a new member named a, it would use the existing one from the base. This leaves us with option 2.

For 2, we thought a bit about the subvariants. We're a bit concerned by field naming conventions differing from parameters: what guarantees are there that a constructor parameter named a will actually assign to a field named a? There are none, of course, only long-standing conventions. Some users prefer to name their fields with _ prefixes (following the C# code-style guidelines), but many keep them exactly the same. We ultimately think that there's enough signal of intent in passing the primary ctor parameter, no matter what the name of the base ctor parameter is, and we can suppress the warning for this case.

Conclusion

Option 2.2: Produce a warning on usage when a base member shadows a primary constructor parameter if that primary constructor parameter was not passed to the base type via its constructor.