110 lines
7.2 KiB
Markdown
110 lines
7.2 KiB
Markdown
# C# Language Design Meeting for October 26st, 2020
|
|
|
|
## Agenda
|
|
|
|
1. [Pointer types in records](#pointer-types-in-records)
|
|
2. [Triage](#triage)
|
|
1. [readonly classes and records](#readonly-classes-and-records)
|
|
2. [Target typed anonymous type initializers](#target-typed-anonymous-type-initializers)
|
|
3. [Static local functions in base calls](#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 retconn 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.
|