csharplang/meetings/2020/LDM-2020-10-26.md

7.2 KiB

C# Language Design Meeting for October 26st, 2020

Agenda

  1. Pointer types in records
  2. Triage
    1. readonly classes and records
    2. Target typed anonymous type initializers
    3. Static local functions in base calls

Quote of the Day

  • "And I specialize in taking facetious questions and answering them literally"

Discussion

Pointer types in records

Today, you cannot use pointer types in records, because our lowering will use EqualityComparer<T>.Default, and pointer types are not allowed as generic type arguments in general. We could specially recognize pointer types here, and use a different equality when comparing fields of that type. We have a similar issue with anonymous types, where pointers are not permitted for the same reason (and indeed, Roslyn's code for generating the equality implementation is shared between these constructs). We would also need consider every place record types can be used if we enabled this: for example, what would the experience be when attempting to pattern deconstruct on a record type, as pointer types are not allowed in patterns today? It also might not be a good idea to introduce value equality based on pointer types to class types, as this is not well-defined for all pointer types (function pointers, for example). Finally, the runtime has talked several times about enabling pointer types as generic type parameters, and if they were to do so then the rules for this might fall out at that time.

Conclusion

Triaged to the Backlog. We're not convinced this needs to be something that we enable right now, and may end up being resolved by fallout from other changes.

Triage

readonly classes and records

https://github.com/dotnet/csharplang/issues/3885

This proposal would allow marking a class type readonly, ensuring that all fields and properties on the type must be readonly as well. Several familiar questions were immediately raised, namely around the benefit. readonly has a very specific benefit for struct types, around allowing the compiler to avoid defensive copies where they would otherwise be necessary. For readonly classes, there is no clear similar advantage. We might not even emit such information to metadata, and the main benefit would be for type authors, not for type consumers. There is also some concern about whether this would be confusing to users, particularly if this does not apply to an entire hierarchy. If you depend on a non-Object base type that has mutable, then the benefits of using readonly are not as clear, even for a type author. Similarly, if a non-readonly type can inherit from a readonly type, that means that any guarantees on the current type aren't very strong, as mutation can occur under the hood anyway. readonly in C# today always means shallow immutability, so there is an argument to be made that this level of hierarchy-mutability is not too different.
We also looked at the question of whether this feature should just be analyzer. There is certainly argument for that: particularly if there is no hierarchy impact, it seems a perfect use case for an analyzer. However, this is a case where we allow the keyword on one set of types, while not allowing it on a different set of types. Further, unlike many such proposals, we already have a C# keyword that is perfect for the scenario.

Conclusion

Triaged into the Working Set. We'll look at this with low priority, and particularly try to see what the scenarios around hierarchical enforcement look like, as those were more generally palatable to LDT members.

Target typed anonymous type initializers

https://github.com/dotnet/csharplang/issues/3957

This is a proposal to address some cognitive dissonance we have with object creation in C#: you can leave off the parens if you have an object initializer, but only if you specify the type. While it does save 2 characters, that is not a primary motivation of this proposal. There are grow-up stories for other areas we could explore in this space as well: we could allow F#-style object expressions, for example, or borrow from Java and allow anonymous types to actually inherit from existing types/interfaces. However, we have a number of concerns about the compat aspects of doing this, where adding a new object overload can silently change consumer code to call a different overload and create an anonymous type. In these types of scenarios, it might even be impossible to determine if the user made an error: if they typed a wrong letter in the property name, for example, we might be forced to create an anonymous type silently, instead of erroring on the invalid object initializer.

We also briefly considered more radical changes to the syntax: for example, could we allow TS/JS-style object creation, with just the brackets? However, this idea was not very well received by the LDM.

Conclusion

Triaged into the Backlog. While we're open to new proposals in this space that significantly shift the bar (such as around new ways of creating anonymous types that inherit from existing types), we think that this proposal could end up conflicting with any such future proposals and should be considered then.

Static local functions in base calls

https://github.com/dotnet/csharplang/issues/3980

This is a proposal that, depending on the exact specification, would either be a breaking change or have complicated lookup rules designed to avoid the breaking change. It also requires some deep thought into how the exact scoping rules would work. Today, locals introduced in the base call are visible throughout the constructor, so we would have to retcon the scoping rules to work something like this:

  1. Outermost scope, contains static local functions
  2. Middle scope, contains the base clause and any variables declared there
  3. Inner scope, contains the method body locals and regular local functions.

This also raises the question of whether we should stop here. For example, it might be nice if const locals could be used as parameter default values, or if attributes could use names from inside a method body. We've had a few proposals for creating various parts of a "method header" scope (such as https://github.com/dotnet/csharplang/issues/373), we could consider extending that generally to allow this type of thing. Another question would be: why stop at static local functions? We could allow regular local functions in the base clause, and leverage definite assignment to continue doing the same things it does today to make sure that things aren't used before assignment. This might work well with a general "method header" scope, instead of the scheme proposed above. Finally, we considered simply allowing the base call to be done in the body of the constructor instead, a la Visual Basic. This has some support, and would allow us to avoid the question of a method header scope by simply allowing users to move the base call to where the local function is visible.

Conclusion

Triaged into the Working Set. We like the idea, and have a few avenues to explore around method header scopes or allowing the base call to be moved.