7.2 KiB
C# Language Design Meeting for October 26st, 2020
Agenda
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:
- Outermost scope, contains static local functions
- Middle scope, contains the base clause and any variables declared there
- 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.