Compare commits
1 commit
main
...
AlekseyTs-
Author | SHA1 | Date | |
---|---|---|---|
a1281e11dd |
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -1 +0,0 @@
|
|||
* @dotnet/roslyn-compiler
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +0,0 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Propose a language idea or ask a question
|
||||
url: https://github.com/dotnet/csharplang/discussions/new
|
||||
about: Starting with discussion is the way to create a new proposal.
|
50
.github/ISSUE_TEMPLATE/proposal_template.md
vendored
50
.github/ISSUE_TEMPLATE/proposal_template.md
vendored
|
@ -1,50 +0,0 @@
|
|||
---
|
||||
name: Create a language specification
|
||||
about: For proposals that have been invited by a team member.
|
||||
title: "[Proposal]: [FEATURE_NAME]"
|
||||
---
|
||||
<!--
|
||||
Hello, and thanks for your interest in contributing to C#! If you haven't been invited by a team member to open an issue, please instead open a discussion marked [draft issue] at https://github.com/dotnet/csharplang/discussions/new and we'll try to give you feedback on how to get to an issue-ready proposal.
|
||||
|
||||
New language feature proposals should fully fill out this template. This should include a complete detailed design, which describes the syntax of the feature, what that syntax means, and how it affects current parts of the spec. Please make sure to point out specific spec sections that need to be updated for this feature.
|
||||
-->
|
||||
# FEATURE_NAME
|
||||
|
||||
* [x] Proposed
|
||||
* [ ] Prototype: Not Started
|
||||
* [ ] Implementation: Not Started
|
||||
* [ ] Specification: Not Started
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
<!-- One paragraph explanation of the feature. -->
|
||||
|
||||
## Motivation
|
||||
[motivation]: #motivation
|
||||
|
||||
<!-- Why are we doing this? What use cases does it support? What is the expected outcome? -->
|
||||
|
||||
## Detailed design
|
||||
[design]: #detailed-design
|
||||
|
||||
<!-- This is the bulk of the proposal. Explain the design in enough detail for somebody familiar with the language to understand, and for somebody familiar with the compiler to implement, and include examples of how the feature is used. Please include syntax and desired semantics for the change, including linking to the relevant parts of the existing C# spec to describe the changes necessary to implement this feature. An initial proposal does not need to cover all cases, but it should have enough detail to enable a language team member to bring this proposal to design if they so choose. -->
|
||||
|
||||
## Drawbacks
|
||||
[drawbacks]: #drawbacks
|
||||
|
||||
<!-- Why should we *not* do this? -->
|
||||
|
||||
## Alternatives
|
||||
[alternatives]: #alternatives
|
||||
|
||||
<!-- What other designs have been considered? What is the impact of not doing this? -->
|
||||
|
||||
## Unresolved questions
|
||||
[unresolved]: #unresolved-questions
|
||||
|
||||
<!-- What parts of the design are still undecided? -->
|
||||
|
||||
## Design meetings
|
||||
|
||||
<!-- Link to design notes that affect this proposal, and describe in one sentence for each what changes they led to. -->
|
|
@ -1,6 +0,0 @@
|
|||
# Code of Conduct
|
||||
|
||||
This project has adopted the code of conduct defined by the Contributor Covenant
|
||||
to clarify expected behavior in our community.
|
||||
|
||||
For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).
|
|
@ -6,13 +6,9 @@
|
|||
|
||||
[![Join the chat at https://gitter.im/dotnet/csharplang](https://badges.gitter.im/dotnet/csharplang.svg)](https://gitter.im/dotnet/csharplang?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
- [Dotnet Discord](https://aka.ms/dotnet-discord-csharp) - github.com/dotnet discord for discussing dotnet repositories (including csharplang).
|
||||
- [Discord](https://aka.ms/csharp-discord) - Any discussion related to the C# language up to the application level.
|
||||
|
||||
[![Chat on Discord](https://discordapp.com/api/guilds/143867839282020352/widget.png)](https://aka.ms/dotnet-discord-csharp)
|
||||
|
||||
- [C# Discord](https://aka.ms/csharp-discord) - General C# discussion not limited to the dotnet repositories.
|
||||
|
||||
[![Chat on Discord](https://discordapp.com/api/guilds/102860784329052160/widget.png)](https://aka.ms/csharp-discord)
|
||||
[![Join the chat at https://aka.ms/csharp-discord](https://img.shields.io/discord/102860784329052160.svg)](https://aka.ms/csharp-discord)
|
||||
|
||||
- IRC - Any discussion related to the C# language up to the application level.
|
||||
|
||||
|
|
|
@ -1,44 +1,111 @@
|
|||
Features Added in C# Language Versions
|
||||
====================
|
||||
|
||||
# C# 10.0 - .NET 6 and Visual Studio 2022 version 17.0
|
||||
# [C# 1.0](https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#.NET_.282002.29) (Visual Studio.NET)
|
||||
|
||||
- [Record structs](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/record-structs.md) and `with` expressions on structs (`record struct Point(int X, int Y);`, `var newPoint = point with { X = 100 };`).
|
||||
- [Global using directives](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/GlobalUsingDirective.md): `global using` directives avoid repeating the same `using` directives across many files in your program.
|
||||
- [Improved definite assignment](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-definite-assignment.md): definite assignment and nullability analysis better handle common patterns such as `dictionary?.TryGetValue(key, out value) == true`.
|
||||
- [Constant interpolated strings](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/constant_interpolated_strings.md): interpolated strings composed of constants are themselves constants.
|
||||
- [Extended property patterns](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/extended-property-patterns.md): property patterns allow accessing nested members (`if (e is MethodCallExpression { Method.Name: "MethodName" })`).
|
||||
- [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174): a record can inherit a base record with a sealed `ToString`.
|
||||
- [Incremental source generators](https://github.com/dotnet/roslyn/blob/main/docs/features/incremental-generators.md): improve the source generation experience in large projects by breaking down the source generation pipeline and caching intermediate results.
|
||||
- [Mixed deconstructions](https://github.com/dotnet/csharplang/issues/125): deconstruction-assignments and deconstruction-declarations can be blended together (`(existingLocal, var declaredLocal) = expression`).
|
||||
- [Method-level AsyncMethodBuilder](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/async-method-builders.md): the AsyncMethodBuilder used to compile an `async` method can be overridden locally.
|
||||
- [#line span directive](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/enhanced-line-directives.md): allow source generators like Razor fine-grained control of the line mapping with `#line` directives that specify the destination span (`#line (startLine, startChar) - (endLine, endChar) charOffset "fileName"`).
|
||||
- [Lambda improvements](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md): attributes and return types are allowed on lambdas; lambdas and method groups have a natural delegate type (`var f = short () => 1;`).
|
||||
- [Interpolated string handlers](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md): interpolated string handler types allow efficient formatting of interpolated strings in assignments and invocations.
|
||||
- [File-scoped namespaces](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/file-scoped-namespaces.md): files with a single namespace don't need extra braces or indentation (`namespace X.Y.Z;`).
|
||||
- [Parameterless struct constructors](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/parameterless-struct-constructors.md): support parameterless constructors and instance field initializers for struct types.
|
||||
- [CallerArgumentExpression](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/caller-argument-expression.md): this attribute allows capturing the expressions passed to a method as strings.
|
||||
- Classes
|
||||
- Structs
|
||||
- Interfaces
|
||||
- Events
|
||||
- Properties
|
||||
- Delegates
|
||||
- Expressions
|
||||
- Statements
|
||||
- Attributes
|
||||
- Literals
|
||||
|
||||
# C# 9.0 - .NET 5 and Visual Studio 2019 version 16.8
|
||||
- [Records](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md) and `with` expressions: succinctly declare reference types with value semantics (`record Point(int X, int Y);`, `var newPoint = point with { X = 100 };`).
|
||||
- [Init-only setters](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/init.md): init-only properties can be set during object creation (`int Property { get; init; }`).
|
||||
- [Top-level statements](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/top-level-statements.md): the entry point logic of a program can be written without declaring an explicit type or `Main` method.
|
||||
- [Pattern matching enhancements](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/patterns3.md): relational patterns (`is < 30`), combinator patterns (`is >= 0 and <= 100`, `case 3 or 4:`, `is not null`), parenthesized patterns (`is int and (< 0 or > 100)`), type patterns (`case Type:`).
|
||||
- [Native sized integers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/native-integers.md): the numeric types `nint` and `nuint` match the platform memory size.
|
||||
- [Function pointers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/function-pointers.md): enable high-performance code leveraging IL instructions `ldftn` and `calli` (`delegate* <int, void> local;`)
|
||||
- [Suppress emitting `localsinit` flag](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/skip-localsinit.md): attributing a method with `[SkipLocalsInit]` will suppress emitting the `localsinit` flag to reduce cost of zero-initialization.
|
||||
- [Target-typed new expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/target-typed-new.md): `Point p = new(42, 43);`.
|
||||
- [Static anonymous functions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/static-anonymous-functions.md): ensure that anonymous functions don't capture `this` or local variables (`static () => { ... };`).
|
||||
- [Target-typed conditional expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/target-typed-conditional-expression.md): conditional expressions which lack a natural type can be target-typed (`int? x = b ? 1 : null;`).
|
||||
- [Covariant return types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/covariant-returns.md): a method override on reference types can declare a more derived return type.
|
||||
- [Lambda discard parameters](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/lambda-discard-parameters.md): multiple parameters `_` appearing in a lambda are allowed and are discards.
|
||||
- [Attributes on local functions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/local-function-attributes.md).
|
||||
- [Module initializers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/module-initializers.md): a method attributed with `[ModuleInitializer]` will be executed before any other code in the assembly.
|
||||
- [Extension `GetEnumerator`](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/extension-getenumerator.md): an extension `GetEnumerator` method can be used in a `foreach`.
|
||||
- [Partial methods with returned values](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/extending-partial-methods.md): partial methods can have any accessibility, return a type other than `void` and use `out` parameters, but must be implemented.
|
||||
- [Source Generators](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/)
|
||||
# [C# 1.2](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-12)
|
||||
|
||||
# C# 8.0 - .NET Core 3.0 and Visual Studio 2019 version 16.3
|
||||
- Dispose in foreach
|
||||
- foreach over string specialization
|
||||
|
||||
# [C# 2](https://msdn.microsoft.com/en-us/library/7cz8t42e(v=vs.80).aspx) (VS 2005)
|
||||
- Generics
|
||||
- Partial types
|
||||
- Anonymous methods
|
||||
- Iterators
|
||||
- Nullable types
|
||||
- Getter/setter separate accessibility
|
||||
- Method group conversions (delegates)
|
||||
- Static classes
|
||||
- Delegate inference
|
||||
|
||||
# [C# 3](https://msdn.microsoft.com/en-us/library/bb308966.aspx) (VS 2008)
|
||||
- Implicitly typed local variables
|
||||
- Object and collection initializers
|
||||
- Auto-Implemented properties
|
||||
- Anonymous types
|
||||
- Extension methods
|
||||
- Query expressions
|
||||
- Lambda expression
|
||||
- Expression trees
|
||||
- Partial methods
|
||||
|
||||
# [C# 4](https://msdn.microsoft.com/en-us/magazine/ff796223.aspx) (VS 2010)
|
||||
- Dynamic binding
|
||||
- Named and optional arguments
|
||||
- Co- and Contra-variance for generic delegates and interfaces
|
||||
- Embedded interop types ("NoPIA")
|
||||
|
||||
# [C# 5](https://blogs.msdn.microsoft.com/mvpawardprogram/2012/03/26/an-introduction-to-new-features-in-c-5-0/) (VS 2012)
|
||||
- Asynchronous methods
|
||||
- Caller info attributes
|
||||
|
||||
# [C# 6](https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6) (VS 2015)
|
||||
- [Draft Specification online](https://github.com/dotnet/csharplang/blob/master/spec/README.md)
|
||||
- Compiler-as-a-service (Roslyn)
|
||||
- Import of static type members into namespace
|
||||
- Exception filters
|
||||
- Await in catch/finally blocks
|
||||
- Auto property initializers
|
||||
- Default values for getter-only properties
|
||||
- Expression-bodied members
|
||||
- Null propagator (null-conditional operator, succinct null checking)
|
||||
- String interpolation
|
||||
- nameof operator
|
||||
- Dictionary initializer
|
||||
|
||||
# [C# 7.0](https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/) (Visual Studio 2017)
|
||||
- [Out variables](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/out-var.md)
|
||||
- [Pattern matching](https://github.com/dotnet/csharplang/blob/master/proposals/patterns.md)
|
||||
- [Tuples](https://github.com/dotnet/roslyn/blob/master/docs/features/tuples.md)
|
||||
- [Deconstruction](https://github.com/dotnet/roslyn/blob/master/docs/features/deconstruction.md)
|
||||
- [Discards](https://github.com/dotnet/roslyn/blob/master/docs/features/discards.md)
|
||||
- [Local Functions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/local-functions.md)
|
||||
- [Binary Literals](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/binary-literals.md)
|
||||
- [Digit Separators](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/digit-separators.md)
|
||||
- Ref returns and locals
|
||||
- [Generalized async return types](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md)
|
||||
- More expression-bodied members
|
||||
- [Throw expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/throw-expression.md)
|
||||
|
||||
# [C# 7.1](https://blogs.msdn.microsoft.com/dotnet/2017/10/31/welcome-to-c-7-1/) (Visual Studio 2017 version 15.3)
|
||||
- [Async main](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/async-main.md)
|
||||
- [Default expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/target-typed-default.md)
|
||||
- [Reference assemblies](https://github.com/dotnet/roslyn/blob/master/docs/features/refout.md)
|
||||
- [Inferred tuple element names](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/infer-tuple-names.md)
|
||||
- [Pattern-matching with generics](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/generics-pattern-match.md)
|
||||
|
||||
# [C# 7.2](https://blogs.msdn.microsoft.com/dotnet/2017/11/15/welcome-to-c-7-2-and-span/) (Visual Studio 2017 version 15.5)
|
||||
- [Span and ref-like types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md)
|
||||
- [In parameters and readonly references](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md)
|
||||
- [Ref conditional](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/conditional-ref.md)
|
||||
- [Non-trailing named arguments](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/non-trailing-named-arguments.md)
|
||||
- [Private protected accessibility](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/private-protected.md)
|
||||
- [Digit separator after base specifier](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/leading-separator.md)
|
||||
|
||||
# C# 7.3 (Visual Studio 2017 version 15.7)
|
||||
- `System.Enum`, `System.Delegate` and [`unmanaged`](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/blittable.md) constraints.
|
||||
- [Ref local re-assignment](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/ref-local-reassignment.md): Ref locals and ref parameters can now be reassigned with the ref assignment operator (`= ref`).
|
||||
- [Stackalloc initializers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/stackalloc-array-initializers.md): Stack-allocated arrays can now be initialized, e.g. `Span<int> x = stackalloc[] { 1, 2, 3 };`.
|
||||
- [Indexing movable fixed buffers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/indexing-movable-fixed-fields.md): Fixed buffers can be indexed into without first being pinned.
|
||||
- [Custom `fixed` statement](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/pattern-based-fixed.md): Types that implement a suitable `GetPinnableReference` can be used in a `fixed` statement.
|
||||
- [Improved overload candidates](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/improved-overload-candidates.md): Some overload resolution candidates can be ruled out early, thus reducing ambiguities.
|
||||
- [Expression variables in initializers and queries](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/expression-variables-in-initializers.md): Expression variables like `out var` and pattern variables are allowed in field initializers, constructor initializers and LINQ queries.
|
||||
- [Tuple comparison](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/tuple-equality.md): Tuples can now be compared with `==` and `!=`.
|
||||
- [Attributes on backing fields](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/auto-prop-field-attrs.md): Allows `[field: …]` attributes on an auto-implemented property to target its backing field.
|
||||
|
||||
# C# 8.0 (Visual Studio 2019 version 16.3, .NET Core 3.0)
|
||||
- [Nullable reference types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/nullable-reference-types-specification.md): express nullability intent on reference types with `?`, `notnull` constraint and annotations attributes in APIs, the compiler will use those to try and detect possible `null` values being dereferenced or passed to unsuitable APIs.
|
||||
- [Default interface members](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/default-interface-methods.md): interfaces can now have members with default implementations, as well as static/private/protected/internal members except for state (ie. no fields).
|
||||
- [Recursive patterns](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-8.0/patterns.md): positional and property patterns allow testing deeper into an object, and switch expressions allow for testing multiple patterns and producing corresponding results in a compact fashion.
|
||||
|
@ -53,121 +120,3 @@ Features Added in C# Language Versions
|
|||
- [Alternative interpolated verbatim strings](https://github.com/dotnet/csharplang/issues/1630): `@$"..."` strings are recognized as interpolated verbatim strings just like `$@"..."`.
|
||||
- [Obsolete on property accessors](https://github.com/dotnet/csharplang/issues/2152): property accessors can now be individually marked as obsolete.
|
||||
- [Permit `t is null` on unconstrained type parameter](https://github.com/dotnet/csharplang/issues/1284)
|
||||
|
||||
# C# 7.3 - Visual Studio 2017 version 15.7
|
||||
- `System.Enum`, `System.Delegate` and [`unmanaged`](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/blittable.md) constraints.
|
||||
- [Ref local re-assignment](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/ref-local-reassignment.md): Ref locals and ref parameters can now be reassigned with the ref assignment operator (`= ref`).
|
||||
- [Stackalloc initializers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/stackalloc-array-initializers.md): Stack-allocated arrays can now be initialized, e.g. `Span<int> x = stackalloc[] { 1, 2, 3 };`.
|
||||
- [Indexing movable fixed buffers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/indexing-movable-fixed-fields.md): Fixed buffers can be indexed into without first being pinned.
|
||||
- [Custom `fixed` statement](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/pattern-based-fixed.md): Types that implement a suitable `GetPinnableReference` can be used in a `fixed` statement.
|
||||
- [Improved overload candidates](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/improved-overload-candidates.md): Some overload resolution candidates can be ruled out early, thus reducing ambiguities.
|
||||
- [Expression variables in initializers and queries](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/expression-variables-in-initializers.md): Expression variables like `out var` and pattern variables are allowed in field initializers, constructor initializers and LINQ queries.
|
||||
- [Tuple comparison](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/tuple-equality.md): Tuples can now be compared with `==` and `!=`.
|
||||
- [Attributes on backing fields](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.3/auto-prop-field-attrs.md): Allows `[field: …]` attributes on an auto-implemented property to target its backing field.
|
||||
|
||||
# [C# 7.2](https://blogs.msdn.microsoft.com/dotnet/2017/11/15/welcome-to-c-7-2-and-span/) - Visual Studio 2017 version 15.5
|
||||
- [Span and ref-like types](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/span-safety.md)
|
||||
- [In parameters and readonly references](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/readonly-ref.md)
|
||||
- [Ref conditional](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/conditional-ref.md)
|
||||
- [Non-trailing named arguments](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/non-trailing-named-arguments.md)
|
||||
- [Private protected accessibility](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/private-protected.md)
|
||||
- [Digit separator after base specifier](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.2/leading-separator.md)
|
||||
|
||||
# [C# 7.1](https://blogs.msdn.microsoft.com/dotnet/2017/10/31/welcome-to-c-7-1/) - Visual Studio 2017 version 15.3
|
||||
- [Async main](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/async-main.md)
|
||||
- [Default expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/target-typed-default.md)
|
||||
- [Reference assemblies](https://github.com/dotnet/roslyn/blob/master/docs/features/refout.md)
|
||||
- [Inferred tuple element names](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/infer-tuple-names.md)
|
||||
- [Pattern-matching with generics](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/generics-pattern-match.md)
|
||||
|
||||
# [C# 7.0](https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/) - Visual Studio 2017
|
||||
- [Out variables](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/out-var.md)
|
||||
- [Pattern matching](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-7.0/pattern-matching.md)
|
||||
- [Tuples](https://github.com/dotnet/roslyn/blob/master/docs/features/tuples.md)
|
||||
- [Deconstruction](https://github.com/dotnet/roslyn/blob/master/docs/features/deconstruction.md)
|
||||
- [Discards](https://github.com/dotnet/roslyn/blob/master/docs/features/discards.md)
|
||||
- [Local Functions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/local-functions.md)
|
||||
- [Binary Literals](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/binary-literals.md)
|
||||
- [Digit Separators](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/digit-separators.md)
|
||||
- [Ref returns and locals](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/ref-returns)
|
||||
- [Generalized async return types](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md)
|
||||
- [More expression-bodied members](https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members)
|
||||
- [Throw expressions](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.0/throw-expression.md)
|
||||
|
||||
# [C# 6](https://github.com/dotnet/roslyn/blob/master/docs/wiki/New-Language-Features-in-C%23-6.md) - Visual Studio 2015
|
||||
- [Draft Specification online](https://github.com/dotnet/csharplang/blob/master/spec/README.md)
|
||||
- Compiler-as-a-service (Roslyn)
|
||||
- [Import of static type members into namespace](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/using-static)
|
||||
- [Exception filters](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/when)
|
||||
- Await in catch/finally blocks
|
||||
- Auto property initializers
|
||||
- Default values for getter-only properties
|
||||
- [Expression-bodied members](https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members)
|
||||
- Null propagator (null-conditional operator, succinct null checking)
|
||||
- [String interpolation](https://docs.microsoft.com/dotnet/csharp/language-reference/tokens/interpolated)
|
||||
- [nameof operator](https://docs.microsoft.com/dotnet/csharp/language-reference/operators/nameof)
|
||||
- Dictionary initializer
|
||||
|
||||
# [C# 5](https://blogs.msdn.microsoft.com/mvpawardprogram/2012/03/26/an-introduction-to-new-features-in-c-5-0/) - Visual Studio 2012
|
||||
- [Asynchronous methods](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/async/)
|
||||
- [Caller info attributes](https://docs.microsoft.com/dotnet/csharp/language-reference/attributes/caller-information)
|
||||
- foreach loop was changed to generates a new loop variable rather than closing over the same variable every time
|
||||
|
||||
# [C# 4](https://msdn.microsoft.com/magazine/ff796223.aspx) - Visual Studio 2010
|
||||
- [Dynamic binding](https://docs.microsoft.com/dotnet/csharp/programming-guide/types/using-type-dynamic)
|
||||
- [Named and optional arguments](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments)
|
||||
- [Co- and Contra-variance for generic delegates and interfaces](https://docs.microsoft.com/dotnet/standard/generics/covariance-and-contravariance)
|
||||
- [Embedded interop types ("NoPIA")](https://docs.microsoft.com/dotnet/framework/interop/type-equivalence-and-embedded-interop-types)
|
||||
|
||||
# [C# 3](https://msdn.microsoft.com/library/bb308966.aspx) - Visual Studio 2008
|
||||
- [Implicitly typed local variables](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/implicitly-typed-local-variables)
|
||||
- [Object and collection initializers](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers)
|
||||
- [Auto-Implemented properties](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties)
|
||||
- [Anonymous types](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/anonymous-types)
|
||||
- [Extension methods](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/extension-methods)
|
||||
- [Query expressions, a.k.a LINQ (Language Integrated Query)](https://docs.microsoft.com/dotnet/csharp/linq/query-expression-basics)
|
||||
- [Lambda expression](https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions)
|
||||
- [Expression trees](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/expression-trees/)
|
||||
- [Partial methods](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/partial-method)
|
||||
- [Lock statement](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/lock-statement)
|
||||
|
||||
# [C# 2](https://msdn.microsoft.com/library/7cz8t42e(v=vs.80).aspx) - Visual Studio 2005
|
||||
- [Generics](https://docs.microsoft.com/dotnet/csharp/programming-guide/generics/)
|
||||
- [Partial types](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/partial-type)
|
||||
- [Anonymous methods](https://docs.microsoft.com/dotnet/csharp/programming-guide/statements-expressions-operators/anonymous-functions)
|
||||
- [Iterators, a.k.a yield statement](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/yield)
|
||||
- [Nullable types](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/nullable-value-types)
|
||||
- Getter/setter separate accessibility
|
||||
- Method group conversions (delegates)
|
||||
- [Static classes](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members)
|
||||
- Delegate inference
|
||||
- Type and namespace aliases
|
||||
- [Covariance and contravariance](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/covariance-contravariance/)
|
||||
|
||||
# [C# 1.2](https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-version-history#c-version-12) - Visual Studio .NET 2003
|
||||
- Dispose in foreach
|
||||
- foreach over string specialization
|
||||
|
||||
# [C# 1.0](https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#.NET_.282002.29) - Visual Studio .NET 2002
|
||||
- [Classes](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/classes)
|
||||
- [Structs](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/struct)
|
||||
- [Enums](https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/enum)
|
||||
- [Interfaces](https://docs.microsoft.com/dotnet/csharp/programming-guide/interfaces/)
|
||||
- [Events](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/event)
|
||||
- [Operator overloading](https://docs.microsoft.com/dotnet/csharp/language-reference/operators/operator-overloading)
|
||||
- [User-defined conversion operators](https://docs.microsoft.com/dotnet/csharp/language-reference/operators/user-defined-conversion-operators)
|
||||
- [Properties](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/properties)
|
||||
- [Indexers](https://docs.microsoft.com/dotnet/csharp/programming-guide/indexers/)
|
||||
- Output parameters ([out](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/out) and [ref](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/ref))
|
||||
- [`params` arrays](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/params)
|
||||
- [Delegates](https://docs.microsoft.com/dotnet/csharp/programming-guide/delegates/)
|
||||
- Expressions
|
||||
- [using statement](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/using-statement)
|
||||
- [goto statement](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/goto)
|
||||
- [Preprocessor directives](https://docs.microsoft.com/dotnet/csharp/language-reference/preprocessor-directives/)
|
||||
- [Unsafe code and pointers](https://docs.microsoft.com/dotnet/csharp/programming-guide/unsafe-code-pointers/)
|
||||
- [Attributes](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/attributes/)
|
||||
- Literals
|
||||
- [Verbatim identifier](https://docs.microsoft.com/dotnet/csharp/language-reference/tokens/verbatim)
|
||||
- Unsigned integer types
|
||||
- [Boxing and unboxing](https://docs.microsoft.com/dotnet/csharp/programming-guide/types/boxing-and-unboxing)
|
||||
|
|
35
README.md
35
README.md
|
@ -1,6 +1,6 @@
|
|||
# C# Language Design
|
||||
|
||||
[![Join the chat at https://gitter.im/dotnet/csharplang](https://badges.gitter.im/dotnet/csharplang.svg)](https://gitter.im/dotnet/csharplang?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Chat on Discord](https://discordapp.com/api/guilds/143867839282020352/widget.png)](https://aka.ms/dotnet-discord-csharp)
|
||||
[![Join the chat at https://gitter.im/dotnet/csharplang](https://badges.gitter.im/dotnet/csharplang.svg)](https://gitter.im/dotnet/csharplang?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
Welcome to the official repo for C# language design. This is where new C# language features are developed, adopted and specified.
|
||||
|
||||
|
@ -17,29 +17,23 @@ If you discover bugs or deficiencies in the above, please leave an issue to rais
|
|||
|
||||
For *new feature proposals*, however, please raise them for [discussion](https://github.com/dotnet/csharplang/labels/Discussion), and *only* submit a proposal as a pull request if invited to do so by a member of the Language Design Team (a "champion").
|
||||
|
||||
## Discussions
|
||||
## Discussion
|
||||
|
||||
Debate pertaining to language features takes place in the form of [Discussions](https://github.com/dotnet/csharplang/discussions) in this repo.
|
||||
Discussion pertaining to language features takes place in the form of issues in this repo, under the [Discussion label](https://github.com/dotnet/csharplang/labels/Discussion).
|
||||
|
||||
If you want to suggest a feature, discuss current design notes or proposals, etc., please [open a new Discussion topic](https://github.com/dotnet/csharplang/discussions/new).
|
||||
If you want to suggest a feature, discuss current design notes or proposals, etc., please [open a new issue](https://github.com/dotnet/csharplang/issues/new), and it will be tagged Discussion.
|
||||
|
||||
Discussions that are short and stay on topic are much more likely to be read. If you leave comment number fifty, chances are that only a few people will read it. To make discussions easier to navigate and benefit from, please observe a few rules of thumb:
|
||||
GitHub is not ideal for discussions, but it is beneficial to have language features discussed nearby to where the design artifacts are. Comment threads that are short and stay on topic are much more likely to be read. If you leave comment number fifty, chances are that only a few people will read it. To make discussions easier to navigate and benefit from, please observe a few rules of thumb:
|
||||
|
||||
- Discussion should be relevant to C# language design. If they are not, they will be summarily closed.
|
||||
- Choose a descriptive topic that clearly communicates the scope of discussion.
|
||||
- Stick to the topic of the discussion. If a comment is tangential, or goes into detail on a subtopic, start a new discussion and link back.
|
||||
- Discussion should be relevant to C# language design. Issues that are not will be summarily closed.
|
||||
- Choose a descriptive title for the issue, that clearly communicates the scope of discussion.
|
||||
- Stick to the topic of the issue title. If a comment is tangential, start a new issue and link back.
|
||||
- If a comment goes into detail on a subtopic, also consider starting a new issue and linking back.
|
||||
- Is your comment useful for others to read, or can it be adequately expressed with an emoji reaction to an existing comment?
|
||||
|
||||
Language proposals which prevent specific syntax from occurring can be achieved with [a Roslyn analyzer](https://docs.microsoft.com/en-us/visualstudio/extensibility/getting-started-with-roslyn-analyzers). Proposals that only make existing syntax optionally illegal will be rejected by the language design committee to prevent increased language complexity.
|
||||
|
||||
## Proposals
|
||||
Once you have a fully fleshed out proposal describing a new language feature in syntactic and semantic detail, please [open an issue for it](https://github.com/dotnet/csharplang/issues/new/choose), and it will be labeled as a [Proposal](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3AProposal). The comment thread on the issue can be used to hash out or briefly discuss details of the proposal, as well as pros and cons of adopting it into C#. If an issue does not meet the bar of being a full proposal, we may move it to a discussion, so that it can be "baked" further. Specific open issues or more expansive discussion with a proposal will often warrant opening a side discussion rather than cluttering the comment section on the issue.
|
||||
|
||||
When a member of the C# LDM finds that a proposal merits discussion, they can [Champion](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22) it, which means that they will bring it to the C# Language Design Meeting. If the LDM decides to work on adopting the feature, the proposer, the champion and others can collaborate on adding it as a document to the [Proposals](proposals) folder, where its evolution can be tracked over time.
|
||||
|
||||
## Design Process
|
||||
|
||||
[Proposals](proposals) evolve as a result of decisions in [Language Design Meetings](meetings), which are informed by [discussions](https://github.com/dotnet/csharplang/discussions), experiments, and offline design work.
|
||||
[Proposals](proposals) are raised by, or on invitation from, "champions" on the LDT. They evolve as a result of decisions in [Language Design Meetings](meetings), which are informed by [discussion](https://github.com/dotnet/csharplang/labels/Discussion), experiments, and offline design work.
|
||||
|
||||
In many cases it will be necessary to implement and share a prototype of a feature in order to land on the right design, and ultimately decide whether to adopt the feature. Prototypes help discover both implementation and usability issues of a feature. A prototype should be implemented in a fork of the [Roslyn repo](https://github.com/dotnet/roslyn) and meet the following bar:
|
||||
|
||||
|
@ -51,15 +45,6 @@ Once approved, a feature should be fully implemented in [Roslyn](https://github.
|
|||
|
||||
**DISCLAIMER**: An active proposal is under active consideration for inclusion into a future version of the C# programming language but is not in any way guaranteed to ultimately be included in the next or any version of the language. A proposal may be postponed or rejected at any time during any phase of the above process based on feedback from the design team, community, code reviewers, or testing.
|
||||
|
||||
### Milestones
|
||||
|
||||
We have a few different milestones for issues on the repo:
|
||||
* [Working Set](https://github.com/dotnet/csharplang/milestone/19) is the set of championed proposals that are currently being actively worked on. Not everything in this milestone will make the next version of C#, but it will get design time during the upcoming release.
|
||||
* [Backlog](https://github.com/dotnet/csharplang/milestone/10) is the set of championed proposals that have been triaged, but are not being actively worked on. While discussion and ideas from the community are welcomed on these proposals, the cost of the design work and implementation review on these features are too high for us to consider community implementation until we are ready for it.
|
||||
* [Any Time](https://github.com/dotnet/csharplang/milestone/14) is the set of championed proposals that have been triaged, but are not being actively worked on and are open to community implementation. Issues in this can be in one of 2 states: needs approved specification, and needs implementation. Those that need a specification still need to be presented during LDM for approval of the spec, but we are willing to take the time to do so at our earliest convenience.
|
||||
* [Likely Never](https://github.com/dotnet/csharplang/milestone/13) is the set of proposals that the LDM has rejected from the language. Without strong need or community feedback, these proposals will not be considered in the future.
|
||||
* Numbered milestones are the set of features that have been implemented for that particular language version. For closed milestones, these are the set of things that shipped with that release. For open milestones, features can be potentially pulled later if we discover compatability or other issues as we near release.
|
||||
|
||||
## Language Design Meetings
|
||||
|
||||
Language Design Meetings (LDMs) are held by the LDT and occasional invited guests, and are documented in Design Meeting Notes in the [meetings](meetings) folder, organized in folders by year. The lifetime of a design meeting note is described in [meetings/README.md](meetings/README.md). LDMs are where decisions about future C# versions are made, including which proposals to work on, how to evolve the proposals, and whether and when to adopt them.
|
||||
|
|
|
@ -274,7 +274,7 @@ A value-semantics class like the above would be automatically generated by a "re
|
|||
class Point(int X, int Y);
|
||||
```
|
||||
|
||||
By default, this would generate all of the above, except parameter names would be upper case. If you want to supersede default behavior, you can give it a body and do that explicitly. For instance, you could make X mutable:
|
||||
By default, this would generate all of the above, except parameter names would be upper case. If you want to supercede default behavior, you can give it a body and do that explicitly. For instance, you could make X mutable:
|
||||
|
||||
``` c#
|
||||
class Point(int X, int Y)
|
||||
|
|
|
@ -112,7 +112,7 @@ We should allow expression variables in queries, but keep them scoped to the ind
|
|||
|
||||
[csharplang/issues/287](https://github.com/dotnet/csharplang/issues/287)
|
||||
|
||||
The [proposal](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/caller-argument-expression.md) calls for an extra parameter with a default value (which is then replaced by the expression passed as an argument for the parameter designated in the attribute). This means that in a tie breaker situation, existing methods would match better than new ones that differ only by having this extra argument.
|
||||
The [proposal](https://github.com/dotnet/csharplang/blob/master/proposals/caller-argument-expression.md) calls for an extra parameter with a default value (which is then replaced by the expression passed as an argument for the parameter designated in the attribute). This means that in a tie breaker situation, existing methods would match better than new ones that differ only by having this extra argument.
|
||||
|
||||
There are solutions to that for API owners:
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
The way `base.` works in classes, if the base implementation that was present at compile
|
||||
time is removed at rune time, the CLR will search for the next implementation in the
|
||||
hierarchy and use that instead. For example,
|
||||
heirarchy and use that instead. For example,
|
||||
|
||||
```C#
|
||||
class A
|
||||
|
|
|
@ -32,8 +32,8 @@ Internally to the member, the preconditions may affect the initial null-state of
|
|||
|
||||
These apply to anything that yields output: out parameters, return values, fields, properties, indexers, etc:
|
||||
|
||||
- `[MaybeNull]`: The output may be null, even if the type disallows it
|
||||
- `[NotNull]`: For outputs (`ref`/`out` parameters, return values), the output will not be null, even if the type allows it. For inputs (by-value/`in` parameters) the value passed is known not to be null when we return.
|
||||
- `[MaybeNull]`: The output may be null, even if the type disallows it
|
||||
- `[NotNull]`: The output will not be null, even if the type allows it
|
||||
|
||||
On invocation, postconditions are applied after generic substitution, and affect the null state of the output. `[MaybeNull]` changes the null state of the output to “may be null”, whereas `[NotNull]` changes it to “not null”.
|
||||
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
# C# Language Design Notes for Aug 26, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
Triage of newly championed issues. Features that we may consider are placed in the milestone where we will look at them again. Listed here by target milestone.
|
||||
|
||||
# Milestone 8.X
|
||||
|
||||
We are unsure if there will be an 8.1 release or we will go straight to C# 9.0. In the latter case we will triage this bucket and move things to 9.0 or later.
|
||||
|
||||
## #883 Zero and one-element tuples
|
||||
|
||||
We don't have a good syntax for one-tuples.
|
||||
|
||||
But in deconstruction and pattern matching there is less of a syntax problem.
|
||||
|
||||
What does `(3)` mean? It is a constant 3. If you want a tuple literal, you can just give it a name - that is unambiguous. `(_: 3)` might become the common pattern, though `_` isn't a discard for tuple element names.
|
||||
|
||||
Could consider splitting the feature and doing only one-element tuples in literals and deconstruction.
|
||||
|
||||
|
||||
# Milestone 9.0
|
||||
|
||||
This is the next major release (as C# 8.0 is a done deal at this point). Putting features in this milestone doesn't mean that we will do them here! It just means that we will consider them in this timeframe - possibly to be rejected or pushed out at that point.
|
||||
|
||||
## #146
|
||||
|
||||
There's something to this, but instead of marking separately, we think it is paired with allowing nullary constructors on structs . For those we would warn on uses of `default(S)` that we can detect, similar to nullability.
|
||||
|
||||
## #812
|
||||
|
||||
Could work with conjunctive patterns. An alternative would be a range-like syntax.
|
||||
|
||||
## #1792, #1793, #1502
|
||||
|
||||
Let's keep ironing out annoying limitations in this space
|
||||
|
||||
## #1881
|
||||
|
||||
Should think also of UTF8
|
||||
|
||||
For `Memory<char>` etc we would probably continue to require you to go through `Span<char>` etc.
|
||||
|
||||
## #2585
|
||||
|
||||
Fits well with a "math" push, and should align with that.
|
||||
|
||||
|
||||
# Milestone X.0
|
||||
|
||||
These would be major features, and we don't expect to consider them for C# 9.0.
|
||||
|
||||
## #339
|
||||
|
||||
This is a very fundamental feature, and we're not sure we have compelling scenarios. Possibly the next thing to look at after "type classes" in the advanced type features category.
|
||||
|
||||
## #1047
|
||||
|
||||
This is probably in pattern matching's future somewhere, though we won't be ready for it in 9.0.
|
||||
|
||||
## #538
|
||||
|
||||
Probably requires runtime work, and the scenarios don't currently justify that.
|
||||
|
||||
## #2545 (Blocked)
|
||||
|
||||
We keep not moving on this. We'd need a good understanding with EF
|
||||
|
||||
|
||||
# Milestone X.X
|
||||
|
||||
These would be minor features which we don't expect to consider until after 9.0.
|
||||
|
||||
## #2608
|
||||
|
||||
Fine, but doesn't rise to priority
|
||||
|
||||
|
||||
# Rejected
|
||||
|
||||
## #301
|
||||
|
||||
This is subsumed by "extension everything" and the other proposals in that ilk. We do understand and support the scenario, but don't want the specific feature.
|
||||
|
||||
## #1033
|
||||
|
||||
Rejected. The assignment should happen explicitly in the body, and the compiler can warn if you forget.
|
||||
|
||||
## #1586
|
||||
|
||||
It shipped and it's in unsafe. We're ok with it.
|
||||
|
||||
|
||||
|
||||
## #2383
|
||||
|
||||
The language is what it is at this point, and it is not worth doing work to "fix" it. However we should add a warning wave warning on the initializers saying they won't ever be called.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
|
||||
# C# Language Design Notes for Aug 28, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Triage
|
||||
|
||||
## Discussion
|
||||
|
||||
### Hiding with optional parameters
|
||||
|
||||
If you define a method with optional parameters with the same name as a method
|
||||
in a base class, we do not provide a warning that the method is hiding the method
|
||||
in the base class, and if you put the `new` keyword, that provides a warning. Do
|
||||
we want to provide a warning in a warning wave that the method is hiding and some
|
||||
mechanism of silencing the warning (presumably allowing `new`).
|
||||
|
||||
**Conclusion**
|
||||
|
||||
If warning waves are here, we're comfortable taking this.
|
||||
|
||||
### `params Span<T>` and string formatting
|
||||
|
||||
There are a lot of important performance implications here. We think it should be
|
||||
done soon, for the latest major release.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Agreed, next major release. (C# 9.0)
|
||||
|
||||
### Allow `sizeof` in safe code
|
||||
|
||||
There have been requests for this over the years, but we're not sure of the
|
||||
actual value of the feature.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Rejected, until we hear compelling use cases.
|
||||
|
||||
### `Tail return` recursive calls
|
||||
|
||||
We could do "immediate" recursion (directly calling the containing method) without
|
||||
depending on the runtime because we could implement it using compiler rewrites, but
|
||||
it does seem like we would want to support mutual recursion or even just tail-dispatch.
|
||||
This would require us to either accept the limitations in the CLR where a `tail` is
|
||||
not always done in constant space, or require a CLR feature that guarantees constant space
|
||||
|
||||
However, we've done without tail calls for a quite a long time and there are some workarounds.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's talk about this later, for a C# 10.0.
|
||||
|
||||
### Variant method overrides
|
||||
|
||||
We previously pushed out overrides with variance (covariant returns, contravariant parameters) due
|
||||
to requiring quadratic stubs to be emitted for the runtime. Now we can explore runtime changes to
|
||||
support variance directly, where the runtime would allow overrides to differ with "compatible"
|
||||
signatures, instead of exact matches, which is what is required right now.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
There's a lot of existing use cases for this feature, and there's some feeling that it may be
|
||||
required for the records feature. Discussion with the runtime says that it would not be
|
||||
very expensive, and is tractable for the next major release. We're scheduling this for C# 9.0.
|
||||
|
||||
### Exponentiation operator (`**`)
|
||||
|
||||
This seems to be the major missing mathematical operator in C#. We like it as part of a broader
|
||||
improvement to mathematical generalization that we're planning in the "shapes" proposals.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
C# 10.0, matched up with shapes.
|
||||
|
||||
### `base(T)` with runtime changes
|
||||
|
||||
This is a follow-up feature for C# 8.0, so we'd like to do it as quickly possible.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Next major release, C# 9.0.
|
||||
|
||||
### Switching on `ReadOnlySpan<char>` with constant strings
|
||||
|
||||
We aren't ready to commit resources to it, since we haven't found blocking issues, but we'd
|
||||
take the improvement whenever it's ready.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Any time.
|
||||
|
||||
### Permit a fixed field to be declared inside a readonly struct
|
||||
|
||||
We'd like to address performance issues soon if they're impactful enough.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Keep it in C# 9.0, pending confirmation of performance impact.
|
||||
|
||||
### Safe fixed-size buffers
|
||||
|
||||
This one has some compelling perf scenarios, but we don't have a clear design nailed down.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We'd like to take in a major release, but we need a solid design first.
|
||||
|
||||
### Comparison, `and`/`or`/`not` patterns
|
||||
|
||||
These seem useful and they are most useful with the `and`/`or` patterns.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
This doesn't seem very big and we think it could fit in any release. C# 9.0 for now. Same
|
||||
for `and`/`or` patterns.
|
||||
|
||||
### Dictionary Literals
|
||||
|
||||
Initialization of collection data structures, including immutable data structures, is a
|
||||
known issue, in both performance and ergonomics. This proposal doesn't really address
|
||||
these problems and special cases Dictionary<K,V>, even though we know the problem also
|
||||
exists for other types.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The proposal in its current form is rejected. We'd rather try to address more problems
|
||||
in a single feature, potentially after the records feature, which has proposals around
|
||||
initialization of immutable types.
|
||||
|
||||
### No-arg constructor/non-defaultable value types
|
||||
|
||||
We're not very excited about it, but there are valid use cases. We previously pulled
|
||||
the feature because there was a runtime bug that did not call the constructor in certain
|
||||
cases. That bug has now been fixed.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
|
||||
### Target-typed conditional expression
|
||||
|
||||
We know it's useful and we have a design that we think would work.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We would like to do this for C# 9.0
|
||||
|
||||
### Surrogate pairs in Unicode-escaped code points in identifiers
|
||||
|
||||
The language spec already says this is allowed.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
If we have a viable implementation, we'll take it whenever.
|
||||
|
||||
### Permit conditional operation with `int?` and double operands
|
||||
|
||||
This is separate from target-typing because it would change the common type algorithm,
|
||||
but it also interacts with the target-typing, so we probably need to do them together.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Discuss before C# 9.0
|
|
@ -1,101 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for September 4, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `[AllowNull]` on properties
|
||||
|
||||
## Discussion
|
||||
|
||||
[https://github.com/dotnet/roslyn/issues/37313](https://github.com/dotnet/roslyn/issues/37313)
|
||||
|
||||
### AllowNull and flow analysis
|
||||
|
||||
Some questions have come up concerning `AllowNull` and property analysis, like in the following example:
|
||||
|
||||
```C#
|
||||
class Program
|
||||
{
|
||||
private string _f = string.Empty;
|
||||
|
||||
[AllowNull]
|
||||
public string P
|
||||
{
|
||||
get => _f;
|
||||
set => _f = value ?? string.Empty;
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
var p = new Program();
|
||||
p.P = null; // No warning, as expected
|
||||
Console.Write(p.P.Length); // unexpected warning
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The tricky part here is that we are tracking flow state for properties, meaning if `null` is stored in a
|
||||
property, we treat that property as having a null state. Although there is an attribute, the flow state
|
||||
of the variable wins, meaning that we think there is a `null` inside `P`, even though the stated type
|
||||
is `string`. Similarly, if you invert the attribute and use `[NotNull]` on a `string?`, the flow state
|
||||
will win again.
|
||||
|
||||
There a couple ideas to address the problem, including
|
||||
|
||||
1. Don't flow-track non-nullable fields/properties.
|
||||
2. Have `[AllowNull]` suppress both the warning, and suppress setting the flow state to `null`
|
||||
3. Have the `[NotNull]` attribute suppress the flow state (which it currently doesn't), and require
|
||||
the property be written with a nullable type (`string?`) and have `[NotNull]` on the getter.
|
||||
4. Stop tracking when any nullability attributes are present on properties.
|
||||
|
||||
|
||||
2 and 3 are somewhat related and we could do both, in theory. The main problem is that it greatly
|
||||
complicates the user's understanding of when flow tracking is enabled, and there are also potentially
|
||||
downstream affects for type inference if we allow the rule for (2) to apply to parameters.
|
||||
|
||||
For (1) it seems plausible, since we would still have a warning on assigning a null to a non-nullable
|
||||
member. This would remove the flow-based subsequent warnings, but the user would still be warned at
|
||||
the point where the problem happens. However, it doesn't seem to solve the problem for generics, e.g.
|
||||
|
||||
```C#
|
||||
class C<T> where T : new()
|
||||
{
|
||||
private T _f = new T();
|
||||
|
||||
[AllowNull]
|
||||
public T P
|
||||
{
|
||||
get => _f ?? throw new Exception();
|
||||
set
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
return _f;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here the generic property `P` cannot be marked nullable because it is unconstrained.
|
||||
|
||||
However, 2 & 3 also have a problem around `MaybeNull`. If a property is annotated `MaybeNull`, then
|
||||
the attribute would override the state, meaning that a null check is useless. If you check for null
|
||||
it would not matter, because when you read the property again, the attribute would override the state
|
||||
and the result would still be maybe null.
|
||||
|
||||
An idea to address this is a combination of (4) and (3), where we have special attribute behavior
|
||||
for properties, and in that case `NotNull` has precedence over nullable state, but nullable state
|
||||
has precedence over `MaybeNull`. In addition, `AllowNull` modifies the state transition to use
|
||||
the declared state if the input is null, while it otherwise uses the non-null state.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The proposal starting point is:
|
||||
|
||||
NotNull wins over tracked state, which wins over MaybeNull.
|
||||
AllowNull transforms an incoming maybe null state to the declared state.
|
||||
|
||||
There's an action item to go investigate how this will play into the rest nullability, and an open
|
||||
question of whether to treat fields like properties, or like local variables.
|
|
@ -1,301 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Sep. 11, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
## Discussion
|
||||
|
||||
### Nullable attributes and flow state interaction
|
||||
|
||||
We started this discussion with an email with a proposal based on follow-up research from the
|
||||
previous meeting.
|
||||
|
||||
### Attribute interaction proposal
|
||||
|
||||
> Allowed inputs and outputs:
|
||||
>
|
||||
> First of all, I’ll try to make rules based only on “allowed inputs” and “allowed outputs” of a
|
||||
> property. I’ll use shorthands ?, ! and T for “nullable”, “nonnullable” and “unknown – depends on
|
||||
> T” respectively.
|
||||
>
|
||||
> For all the different sensible combinations of attributes, here are the “allowed inputs” and “allowed outputs”:
|
||||
|
||||
|
||||
> | | Allowed input | Allowed output |
|
||||
> |----------------------------|---------------|----------------|
|
||||
> | string | ! | ! |
|
||||
> | [AllowNull]string | ? | ! |
|
||||
> | [NotNull]string? | ? | ! |
|
||||
> | [MaybeNull]string | ! | ? |
|
||||
> | [DisallowNull]string? | ! | ? |
|
||||
> | string? | ? | ? |
|
||||
> | [DisallowNull][NotNull]T | ! | ! |
|
||||
> | [NotNull]T | T | ! |
|
||||
> | [AllowNull][NotNull]T | ? | ! |
|
||||
> | [DisallowNull]T | ! | T |
|
||||
> | T | T | T |
|
||||
> | [AllowNull]T | ? | T |
|
||||
> | [DisallowNull][MaybeNull]T | ! | ? |
|
||||
> | [MaybeNull]T | T | ? |
|
||||
> | [AllowNull][MaybeNull]T | ? | ? |
|
||||
|
||||
> There should be no surprises to anyone there. Now let’s use these to define the different behaviors around properties:
|
||||
> Ordering of states: T is stricter than ? and ! is stricter than both T and ?.
|
||||
> This is a measure of relative permissiveness of states.
|
||||
|
||||
> Initial state: The initial state of a property is its allowed output.
|
||||
> This corresponds to us knowing nothing about the property yet, beyond what it tells us through a combination of its type and its postconditions.
|
||||
|
||||
> State after null check:
|
||||
>
|
||||
> - On the non-null branch of a null check the state of the property is !.
|
||||
> - On the null branch of a pure null check the state of the property is ?.
|
||||
> - Elsewhere the state of the property is unchanged.
|
||||
>
|
||||
> These rules reflect the general benefit of a null check, as well as the overriding effect of a pure null check even of a nonnull property.
|
||||
|
||||
Note that this rules out "dangerous" properties, meaning properties that may change outside the
|
||||
scope of the nullable analysis, as in fields which may be changed by a different thread, and thus
|
||||
the null check is unreliable. We don't consider this scenario to be in scope of our current
|
||||
design and if we decide to address this, we must create a new attribute or some other mechanism.
|
||||
|
||||
There's also some problem with the state after null checks, namely that the type may not support
|
||||
the `?` state. For instance, in the following unconstrained generic,
|
||||
|
||||
```C#
|
||||
T M<T>(T t)
|
||||
{
|
||||
if (t != null)
|
||||
t.ToString();
|
||||
return t;
|
||||
}
|
||||
```
|
||||
|
||||
we should not produce a warning on the return, since the state should match the legal state of
|
||||
`T`, which is `T`, not `?`. Similarly, for non-Nullable value types, the state cannot be `?`
|
||||
after a null check, since the value cannot be null. The rule should use the `T` state.
|
||||
|
||||
> State after assignment:
|
||||
>
|
||||
> The state of the property after an assignment is
|
||||
>
|
||||
> - Its initial state if the state of the assigned value is at least as strict as the allowed input, but no stricter than the allowed output
|
||||
> - The state of the assigned value otherwise
|
||||
>
|
||||
> This rule reflects that a property is expected to “take care of things” when the state of an assigned value is valid as input but not as output. It does so by assuming that the resulting state in such situations is something that’s valid as output.
|
||||
>
|
||||
> This is probably the only rule that would differ from the rules for fields, which would continue to always use the state of the assigned value.
|
||||
|
||||
> Warnings on assignment: A warning is yielded if the state of the assigned value is less strict than the allowed input.
|
||||
> This is the same rule as all other input positions.
|
||||
|
||||
|
||||
We like this new "state after assignment" rule and think it can be implemented now.
|
||||
|
||||
However, when looking into the solution, we found that this is the current behavior for properties when annotated:
|
||||
|
||||
```C#
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
#nullable enable
|
||||
|
||||
class C<T> where T : class?
|
||||
{
|
||||
public C(T x) => f = x;
|
||||
T f;
|
||||
T P1 { get => f; set => f = value; }
|
||||
[AllowNull] T P2 { get => f; set => f = value ?? throw new ArgumentNullException(); }
|
||||
[MaybeNull] T P3 { get => default!; set => f = value; }
|
||||
|
||||
void M()
|
||||
{
|
||||
P1 = null; // Warning
|
||||
P2 = null; // No warning
|
||||
P3 = null; // Warning
|
||||
|
||||
f = P1; // No warning
|
||||
f = P2; // No warning
|
||||
f = P3; // BUG?: No warning!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That last line does not look right. Similar to `default(T)`, you could be producing a
|
||||
potentially nullable value, when the substituted type may not permit it. We think
|
||||
the three state domain outlined above will solve the problem, but that would
|
||||
be too extensive to change to perform in such a short period of time.
|
||||
|
||||
Alternative: whenever you introduce a value (by calling
|
||||
a property or a method), that produces a generic type annotated with `[MaybeNull]`
|
||||
in a substituted generic method, that would produce a warning. This
|
||||
matches our current behavior for `default(T)`.
|
||||
|
||||
For example,
|
||||
|
||||
```C#
|
||||
T M<T>()
|
||||
{
|
||||
_ = (new List<T>).FirstOrDefault(); // this would now produce a warning
|
||||
}
|
||||
```
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's implement the "state after assignment" rule as defined and implement the
|
||||
"Alternative" proposal outlined above. We will consider updating to use the
|
||||
"three state domain" above later, which may have some further changes. A sample
|
||||
of the expected behavior follows:
|
||||
|
||||
Non-Generic
|
||||
|
||||
```C#
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
class Widget {
|
||||
string _description = string.Empty;
|
||||
|
||||
[AllowNull]
|
||||
string Description {
|
||||
get => _description;
|
||||
set => _description = value ?? string.Empty;
|
||||
}
|
||||
|
||||
static void Test(Widget w) {
|
||||
w.Description = null; // ok
|
||||
Console.WriteLine(w.Description.ToUpper()); // ok
|
||||
|
||||
if (w.Description == null) {
|
||||
Console.WriteLine(w.Description.ToUpper()); // warning
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Generic
|
||||
|
||||
```C#
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
class Box<T> {
|
||||
T _value;
|
||||
|
||||
[AllowNull]
|
||||
T Value {
|
||||
get => _value;
|
||||
set {
|
||||
if (value != null) {
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void TestConstrained<U>(Box<U> box) where U : class {
|
||||
box.Value = null; // ok
|
||||
Console.WriteLine(box.Value.ToString()); // ok
|
||||
|
||||
if (box.Value == null) {
|
||||
Console.WriteLine(box.Value.ToString()); // warning
|
||||
}
|
||||
}
|
||||
|
||||
static void TestUnconstrained<U>(Box<U> box, U value) {
|
||||
box.Value = default(U); // 'default(U)' always produces a warning when U could be a non-nullable reference type
|
||||
Console.WriteLine(box.Value.ToString()); // ok
|
||||
|
||||
box.Value = value; // ok
|
||||
Console.WriteLine(box.Value.ToString()); // ok
|
||||
|
||||
if (box.Value == null) {
|
||||
Console.WriteLine(box.Value.ToString()); // warning
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## More triage
|
||||
|
||||
### Top-level statements and member declarations
|
||||
|
||||
We have a variety of different use cases and experimental products (C# Interactive Window,
|
||||
Jupyter projects, try.net, etc) that use the current "C# scripting" language, which is already
|
||||
effectively a dialect of C#. There's a fair amount of concern that if adoption continues, we may produce a fracturing of the C# language.
|
||||
|
||||
However, adding top-level statements and reconciling scripting in C# proper would be an expensive
|
||||
feature, in both design and implementation. It also doesn't directly impact many of the designs
|
||||
we're currently considering.
|
||||
|
||||
But there is also significant cost to doing nothing. We have not considered the semantic for many
|
||||
features in C# 8, or even if they should work in scripting (`using` declarations, notably). There
|
||||
is a significant ongoing cost here, either in considering all our designs for the scripting
|
||||
dialect, or in risk that not doing design/implementation work will cause bad experiences for
|
||||
products using the C# scripting code.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We'll schedule this for 9.0, to at least examine options.
|
||||
|
||||
### Primary constructors
|
||||
|
||||
This occupies the same design space as records, which is scheduled for 9.0, so
|
||||
we at least need to consider this feature while implementing records.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Moving to 9.0.
|
||||
|
||||
### Negated-condition if statement
|
||||
|
||||
Issue #882
|
||||
|
||||
This overlaps significantly with a "is not" pattern. We're not confident this
|
||||
feature has significant value, after the "is not" pattern is implemented.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Move to X.X to consider after "is not" has shipped and see if there are significant
|
||||
use cases that are not addressed by the "is not" pattern.
|
||||
|
||||
### Allow `default` in deconstruction
|
||||
|
||||
Issue #1394
|
||||
|
||||
The primary use case is `(x, y, z) = default;` instead of naming each variable
|
||||
individually. There are some issues around the written specification, specifically
|
||||
on what target typing `default` has.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
From a consistency perspective it seems like this should work, regardless of the
|
||||
complexity in details of the specification. We'll take this Any Time whenever we have a solid
|
||||
specification and implementation.
|
||||
|
||||
### Partial type inference
|
||||
|
||||
Issue #1349
|
||||
|
||||
Nothing that's related to type inference is a tiny feature, but this is pretty
|
||||
small as type inference changes are concerned. We think the hardest problem will
|
||||
be agreeing on the syntax. Agreed that it could be useful, though.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We'll take this Any Time.
|
||||
|
||||
### Declaration expressions
|
||||
|
||||
Issue #973
|
||||
|
||||
Somewhat related is "sequence expressions". This is useful for declaring variables inline in an
|
||||
expression. There are places where statements are not possible, and this requires
|
||||
refactoring.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We don't think there's value in half measures here. We think going all the way to sequence
|
||||
expressions may have value, but then declaration expressions do not.
|
|
@ -1,121 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for September 16, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. UTF-8 strings (https://github.com/dotnet/corefxlab/issues/2350)
|
||||
2. Triage
|
||||
|
||||
## Discussion
|
||||
|
||||
### UTF-8 Strings
|
||||
|
||||
Motivation: UTF-8 is everywhere, and converting to and from the .NET native string type (UCS2/UTF-16),
|
||||
can be expensive and confusing.
|
||||
|
||||
#### Language impact
|
||||
|
||||
**Literals**
|
||||
|
||||
Current proposal is to emit the data as UTF-16, and use a runtime helper to project to UTF-8. The
|
||||
main question for the language is if this encoding limits the usability in some way, and whether
|
||||
it blocks us off from introducing an optimal strategy later. There are two things to consider: the
|
||||
cost in the compiler, and the cost to the language. At the moment we don't see anything that would
|
||||
be considered a breaking change in the language.
|
||||
|
||||
**Enumeration**
|
||||
|
||||
The proposal doesn't include any default enumeration. It's worth considering if this violates C#
|
||||
user expectations. There are different forms of enumeration available by calling properties, but
|
||||
none of them are the default. Should there be a default?
|
||||
|
||||
One problem is that users who see enumeration may expect indexing, which is not cheap, and does
|
||||
not match expectations. Another problem is that there is almost always a better operation than
|
||||
enumerating a UTF-8 string. It seems like adding this more likely to encourage a user to write a
|
||||
bug, or inefficient code, than to help them.
|
||||
|
||||
**Why language support**
|
||||
|
||||
The main advantages of language support are:
|
||||
|
||||
* O(n) string concatenation (calling utf8string.Concat with all `n` arguments)
|
||||
* String literals
|
||||
|
||||
**Target-typing**
|
||||
|
||||
Target-typing of existing string literals is possible but produces "bad" behavior for seemingly
|
||||
the most common scenario:
|
||||
|
||||
```C#
|
||||
void M(string s) { ... }
|
||||
void M(Utf8String s) { ... }
|
||||
|
||||
M("some string"); // This would call the `string` version, because backwards compatibility requires
|
||||
// "" always be a `string` first, and a `Utf8String` second
|
||||
```
|
||||
|
||||
**Syntax**
|
||||
|
||||
As always, there's debate about the syntax. The "u" prefix is somewhat unsatisfying because UTF-8,
|
||||
UTF-16, UCS-2, *and* `unsigned` all begin with "u". The same complaints hold for the proposed
|
||||
`ustring` contextual keyword. However, there are no enthusiastically favored alternatives.
|
||||
|
||||
In addition, the value of the `ustring` contextual keyword seems questionable. If the brevity is
|
||||
important, then it seems important enough that the framework could call it `ustring` or `utf8`.
|
||||
One argument is that Utf8String could be a new "primitive" type, and we should add a keyword for
|
||||
all primitive types. However, this is not proposed as a primitive type at the same level, so that
|
||||
weakens support. In addition, the original aliases were all added at the inception of the language,
|
||||
so we have no precedence for adding either primitive types or aliases.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We think the feature is valuable and probably worth some language support. We think a literal syntax
|
||||
(like the "u" prefix) is good, but we don't like target typing. We're also not convinced of the need
|
||||
for a contextual keyword.
|
||||
|
||||
### More triage
|
||||
|
||||
#### Null parameter checking
|
||||
|
||||
Issue #2145
|
||||
|
||||
ASAP, 9.0
|
||||
|
||||
### CallerArgumentExpression
|
||||
|
||||
Issue #287
|
||||
|
||||
We could potentially use this to implement "Null parameter checking" as a method call, instead of
|
||||
new syntax. Thus, consider for 9.0.
|
||||
|
||||
### Relax ordering constraints about modifiers (especially `ref` and `partial`)
|
||||
|
||||
Issue #946
|
||||
|
||||
9.0
|
||||
|
||||
### Zero- and one-element tuples
|
||||
|
||||
Issue #883
|
||||
|
||||
In terms of language value, zero- and one-element tuples are different features. Zero-element
|
||||
tuples are most useful as a unit type, which is not necessarily the unit type we would choose
|
||||
to standardize on (e.g. against System.Void), while one-element tuples are simply useful as a
|
||||
wrapper type and don't have an obvious competitor.
|
||||
|
||||
We need to revisit the decisions here. Moving to X.X
|
||||
|
||||
### Mix declarations and variable in deconstruction
|
||||
|
||||
Issue #125
|
||||
|
||||
Not an urgent feature, but useful for completeness. On the other hand, it's always very clear
|
||||
whether or not the deconstruction is using existing variables, or creating new ones.
|
||||
|
||||
Any time
|
||||
|
||||
### Discard for lambda parameters
|
||||
|
||||
Issue #111
|
||||
|
||||
Any time
|
|
@ -1,98 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for September 18th, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
Triage:
|
||||
|
||||
1. Proposals with complete designs:
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/1888 Champion "Permit attributes on local functions"
|
||||
- https://github.com/dotnet/csharplang/issues/100 Champion "Target-typed new expression"
|
||||
|
||||
2. Target typing and best-common-type features:
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/2473 Proposal: Target typed null coalescing (??) expression
|
||||
- https://github.com/dotnet/csharplang/issues/2460 Champion: target-typed conditional expression
|
||||
- https://github.com/dotnet/csharplang/issues/881 Permit ternary operation with int? and double operands
|
||||
- https://github.com/dotnet/csharplang/issues/33 Champion "Nullable-enhanced common type"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Attributes on local functions
|
||||
|
||||
Issue #1888
|
||||
|
||||
The only major concern about the feature is whether or not the attributes are guaranteed to be emitted to
|
||||
the lowered method, assuming there is a lowered method. For all current lowering strategies, there is a
|
||||
specific place to put the attributes. The proposal is that we always emit attributes for our current
|
||||
scenarios, but don't guarantee in the language that all attributes will survive rewriting.
|
||||
|
||||
There is one exception to the above, which is static local functions. Static local functions are specified
|
||||
to always emit a static method with the same signature as the local function (although there are bugs about
|
||||
this today). Attributes on static local function would have a similar guarantee, meaning that they are always
|
||||
emitted to the lowered methods, on the exact same parameters they were applied to.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Proposal accepted, attributes will be allowed on local functions. `extern` will also be allowed on
|
||||
static local functions, and will be a valid P/Invoke target.
|
||||
|
||||
### Target-typed new
|
||||
|
||||
Issue #100
|
||||
|
||||
We like the feature and already have a design. However, we want to do a quick review that we still like the
|
||||
design, taking into account all the changes we've made to the language since it was proposed.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Accepted, pending review.
|
||||
|
||||
### Target-typed conditional expression and nullable-enhanced common type
|
||||
|
||||
Issue #2460 and issue #33
|
||||
|
||||
The main problem is that providing a target-typing for the switch expression
|
||||
may have made issue #33 a breaking change. In general, additional target-typing
|
||||
(on top of a natural type) works by calculating the target type if there is no
|
||||
viable natural type. By allowing more natural types, it reduces the ability to
|
||||
use target-typing, which may allow more resulting types than the natural type
|
||||
inference.
|
||||
|
||||
Overload resolution is a good example. If you have two methods
|
||||
|
||||
```C#
|
||||
void M(int? x) { ... }
|
||||
void M(short? y) { ... }
|
||||
```
|
||||
|
||||
and a call `M(e switch { ... => 1, ... => null })`, when we do overload resolution
|
||||
we consider the target type for the `switch`, which consists of the target type
|
||||
for each of the candidates. For target type, `short` is a better target for `int`.
|
||||
If we improved the natural type, then the natural type of `1` would be `int`. Then,
|
||||
if we improve the natural type of `e switch { ... => 1, ... => null}` to be `int?`,
|
||||
then a different overload (`M(int? x)`) will be chosen.
|
||||
|
||||
There's also a pretty clear trade-off here. Target-typing may allow more viable
|
||||
types, but it falls over in the presence of type inference or if the target is
|
||||
so broad as to be useless (like if the target-type is `object`).
|
||||
|
||||
Additionally, the proposal for target-typing the conditional expression (`?:`) will have
|
||||
different semantics from the target-typing for the switch expression, to avoid a breaking change.
|
||||
It may be desirable to unify the behavior of things like `?:` and switch expression, even if that
|
||||
means that the switch expression would be more limited, due to the backwards compatibility
|
||||
constraints of `?:`.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's do some work to see if we can alter the interaction of target-typing and common type
|
||||
inference to add the proposed new common types without introducing breaking changes with
|
||||
target-typing. The hope is that such a rule could be general enough to apply to other potential
|
||||
breaks in the future (in case we find more potential improvements to common type).
|
||||
|
||||
In addition, we would like to know the effect of changing switch expressions to prefer the common
|
||||
type, if one exists. We hope that any difference would be uncommon in practice, but it would be
|
||||
useful to know if there are common instances where this would break. This would be used to decide
|
||||
if we would adjust the behavior of the switch expression to match the proposed behavior for the
|
||||
conditional expression.
|
|
@ -1,116 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Oct. 21
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Records
|
||||
2. Init-only members
|
||||
3. Static lambdas
|
||||
|
||||
## Discussion
|
||||
|
||||
We'd like to make some progress on records, in both design and implementation. One development in
|
||||
our view of records is to consider them not as a feature in and of itself, but a shorthand for a
|
||||
set of features aimed at working with a collection of data. Specifically, we have a proposal to
|
||||
consider a guiding principle: a record should only be capable of generating code that the user
|
||||
could write themselves.
|
||||
|
||||
To that end, we can look at the set of features we're considering incorporating into records, and
|
||||
differentiate between them based on the uncertainty of their design. The features which may have
|
||||
generated behavior are:
|
||||
|
||||
1. Automatic structural equality
|
||||
|
||||
2. With-ers (per type?)
|
||||
|
||||
3. Object initializers for readonly (per member)
|
||||
|
||||
4. Primary constructors + deconstruction
|
||||
|
||||
Some features, like with-ers and object initializers, have many open issues. Some
|
||||
features, like generation of structural equality, have widely agreed upon semantics,
|
||||
once the set of members that are part of the structure are decided.
|
||||
|
||||
It's proposed that we take a subset of the features, namely primary constructors and structural equality, and consider them to have designs ready for implementation. Primary constructors still have open semantic questions, especially around their meaning outside of a records, but all of the proposed records specify some type of primary constructor with very similar semantics. An example of the common semantics would be the following,
|
||||
|
||||
```C#
|
||||
data class Person(string Name, DateTime Birthday);
|
||||
```
|
||||
|
||||
which would generate two record members, `Name` and `Birthday`, structural equality on those two
|
||||
members, and a corresponding constructor/deconstructor pair. The `data` modifier, as well as the
|
||||
primary constructor, are not necessarily final syntax, but the semantic decisions downstream of
|
||||
this stand-in don't heavily depend on the specific form of syntax chosen and could be easily
|
||||
changed.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
This looks like a reasonable starting point. Records and the components should
|
||||
definitely be designed together, to ensure they fit together well, but it's worth
|
||||
highlighting the more settled pieces as we go.
|
||||
|
||||
#### Init-only
|
||||
|
||||
A brief discussion of the init-only fields feature. One clarification: the
|
||||
init-only properties would be allowed to have setters. Those setters can be
|
||||
executed in the object initializer, or in the constructor of the object. There
|
||||
is a proposal to allow them also to be set in the constructors of base types,
|
||||
which has no opposition at the moment.
|
||||
|
||||
There's also a problem with "required" init-only members as currently proposed.
|
||||
The current design specifies that "required" init-only members (members without
|
||||
an initializer) would produce an error on the construction side if the member
|
||||
is not initialized in an object initializer.
|
||||
|
||||
For example,
|
||||
|
||||
```C#
|
||||
class Person
|
||||
{
|
||||
public initonly string Name { get; }
|
||||
}
|
||||
|
||||
void M()
|
||||
{
|
||||
var person1 = new Person() { Name = "person1" }; // OK
|
||||
var person2 = new Person(); // Error, Name was not initialized
|
||||
}
|
||||
```
|
||||
|
||||
Unfortunately, the proposed emit strategy for `initonly` members would only provide
|
||||
an error in source, not in metadata. If a consumer compiled against a previous
|
||||
implementation of a type, and a required `initonly` member was added, no error would
|
||||
be provided if the consumer were not recompiled. Instead, the member would silently
|
||||
be set to the default value.
|
||||
|
||||
A simple alternative is to drop "required" `initonly` members entirely. Setting an initonly
|
||||
member would be optional, as it is for public readonly members today, and if it is not set it
|
||||
would retain its default value. The recommendation for adding required members would be the same
|
||||
as it is today: use a constructor parameter, which cannot be skipped.
|
||||
|
||||
We could also attempt to repair the situation by lowering the required `initonly`
|
||||
members into constructor parameters, but this seems undesirable for many reasons,
|
||||
including that it's complex to implement, it risks creating collisions with
|
||||
constructor overloads with the same type, it creates a mismatch in the number of
|
||||
parameters between source and IL, etc. It does not seem worth going down this path.
|
||||
|
||||
|
||||
### Static lambdas
|
||||
|
||||
Static lambdas were elided mostly for time reasons and we're interested in bringing
|
||||
them back. The main hang-up on the syntax is adding modifiers before the lambda
|
||||
syntax, but we already crossed that bridge with `async`.
|
||||
|
||||
The only semantics question is whether or not a static lambda guarantees that the
|
||||
emitted method will be static in metadata. This is true for static local functions,
|
||||
but there are some important scenarios, like `extern` local functions, which depend
|
||||
on that and could not be implemented for lambdas. In addition, there have been
|
||||
numerous performance improvements in lambdas and local functions that have taken
|
||||
advantage of their unspecified metadata characteristics, and at least one significant
|
||||
performance optimization for lambdas would be lost by requiring them to be static.
|
||||
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The `static` keyword will be allowed on lambdas and it will have the same capturing rules as
|
||||
static local functions. It will not require that the lambda be emitted as static.
|
|
@ -1,92 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Oct. 23, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. New primitive types (`nint` and `nuint`)
|
||||
|
||||
## Discussion
|
||||
|
||||
### Native int
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/435
|
||||
|
||||
#### Static members
|
||||
|
||||
The current spec states that `nint` and `nuint` are reserved keywords in a type context but there
|
||||
are contexts, like qualified names, that allow types but are not type contexts. Since `nint` and
|
||||
`nuint` have static members, we want users to be able to access them, so they should be allowed
|
||||
in qualified names.
|
||||
|
||||
#### IntPtr members
|
||||
|
||||
A follow-up question is what to do about existing members on the underlying IntPtr
|
||||
type. There are some members, like Add and Subtract, which may be misleading because
|
||||
they are not equivalent to `+` and `-` due to over/underflow checking.
|
||||
|
||||
There are two views here. On the one hand, the proposal to use IntPtr means that
|
||||
some implementation details for IntPtr will inevitably leak through. Since users
|
||||
will sometimes want to use an IntPtr as an `nuint` to do pointer arithmetic, any
|
||||
methods on IntPtr may be useful and this would just be making barriers.
|
||||
|
||||
On the other hand, `nint` is being added as a type for conceptually different reasons. It
|
||||
shouldn't be assumed that everything that applies to IntPtr should be applicable to `nint`.
|
||||
There's a follow-up question as whether we would exclude everything on IntPtr except for an
|
||||
include list, or include everything except for an exclude list. These may seem very different,
|
||||
but after more thought it's likely they're very similar. Since the framework would probably very
|
||||
rarely add new members to IntPtr after the language change (like with System.Int32), what we pick
|
||||
now is probably what will be present for a very long time, and anything new would probably be
|
||||
done with consultation from the language team.
|
||||
|
||||
Our tentative conclusion is that we should exclude some members. The following
|
||||
are questionable:
|
||||
|
||||
* Zero (use the `0` literal)
|
||||
* Size (use sizeof)
|
||||
* Add, Subtract (part of the point of this type is to use "proper" C# arithmetic)
|
||||
* ToInt32, ToInt64, ToPointer (use the language-defined conversions)
|
||||
|
||||
#### Signed bitwise operations
|
||||
|
||||
Unfortunately, IntPtr is "signed" but is often used to represent pointers, which are unsigned
|
||||
according to framework guidelines. In the current design, IntPtr has an identity conversion to
|
||||
`nint`, which could cause the unfortunate scenario
|
||||
|
||||
```C#
|
||||
IntPtr M(IntPtr p)
|
||||
{
|
||||
nint x = p;
|
||||
x >> 1;
|
||||
return x;
|
||||
}
|
||||
```
|
||||
|
||||
This would use signed `>>`, which is probably incorrect if the value is assumed to be an unsigned
|
||||
pointer. This is not currently a problem with IntPtr because IntPtr does not define any bitwise
|
||||
operators.
|
||||
|
||||
Unfortunately, keeping an identity conversion is an important part of compatibility. We would
|
||||
like to support users who are currently using IntPtr, but would like to use `nint`, to go from
|
||||
`IntPtr[]` to `nint[]`. Without an identity conversion, we don't see how this would be possible
|
||||
in the language today.
|
||||
|
||||
Adding an entirely new concept in the language to support this without identity conversions is
|
||||
probably not worth it. Unless we can find a simple improvement to the design, we will probably
|
||||
have to accept this behavior.
|
||||
|
||||
#### Overriding, hiding, and implementation (OHI)
|
||||
|
||||
Since IntPtr and nint are the same CLR underlying type, they will be regarded as having the same
|
||||
signature. The compiler is able to see the difference. Should we regard them as identical for the
|
||||
purposes of OHI? This is similar to scenarios like tuple names, which are represented as
|
||||
attributes and thus not visible as part of the CLR type. However, in OHI we provide an error if
|
||||
tuple names change in an override because this could be the source of a bug (e.g., if the names
|
||||
were accidentally swapped). In this case we think IntPtr and `nint` are similar enough that we
|
||||
should regard the OHI behavior similar to `dynamic`/`object`, where it is allowed.
|
||||
|
||||
For use as an enum underlying type, we think `nint`/`nuint` should be allowed as long as it's not
|
||||
too much implementation work. The legal values would be the same as the valid constants for the
|
||||
underlying.
|
||||
|
||||
For sizeof, we think it should be supported in unsafe code, just like `sizeof(IntPtr)`, but with
|
||||
no special behavior in safe code.
|
|
@ -1,113 +0,0 @@
|
|||
|
||||
# C# Language Design Notes for Oct. 28, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Discard parameters in lambdas and other methods
|
||||
1. Enhancing the common type algorithm
|
||||
|
||||
## Discussion
|
||||
|
||||
### Discard parameters
|
||||
|
||||
We wanted to talk about discard parameters in lambdas, and also potential expansions into other
|
||||
parameter lists like in local functions and regular methods.
|
||||
|
||||
The first hurdle is whether any form of this feature is worth the complexity. The simplest case
|
||||
is lambdas, because the parameter names are not visible to the caller so they are only visible to
|
||||
the implementation. The alternative to discards is to give throw-away names, like
|
||||
`_1`, `_2`, and `_3`, or to use anonymous method syntax (`delegate { }`) to ignore
|
||||
all parameters.
|
||||
|
||||
As for value, this is a fairly commonly requested requested feature ever since we introduced
|
||||
discards. The cost is increased complexity in the language (understanding that discards are legal
|
||||
as lambda parameters), but there's also an argument that not having the feature causes more
|
||||
complexity in the language. Specifically, the anonymous method syntax is almost entirely obsolete
|
||||
compared to the lambda syntax, but is still commonly used in this exact scenario. If usage of
|
||||
this feature decreases the usage of anonymous method syntax, that could be a decrease in required
|
||||
understanding of the language.
|
||||
|
||||
For discards in local functions and method parameter lists, the cost/value ratio is not nearly as
|
||||
clear. The fundamental limitation is that parameter names for methods and local functions are
|
||||
always public surface area to the caller. We find the cost in complexity in resolving these
|
||||
questions higher than the feature is currently worth. If we find that it becomes a highly desired
|
||||
feature later, we would reconsider this decision.
|
||||
|
||||
Lastly, we had a question of how the scoping would work regarding `_` in both
|
||||
the enclosing scope and inner scope of lambdas.
|
||||
|
||||
```C#
|
||||
void M()
|
||||
{
|
||||
int _ = 0;
|
||||
Action<int, int> a = (_, _) =>
|
||||
{
|
||||
_ = 1; // Is this a discard, or does it capture the local above?
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
We considered various options, like making `_` always be a discard in a lambda body when the
|
||||
lambda parameters are discards, but we have a different precedent for non-lambda
|
||||
scopes like the following:
|
||||
|
||||
```C#
|
||||
void M()
|
||||
{
|
||||
int _ = 0;
|
||||
{
|
||||
_ = 1; // This assigns to the variable _
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We decided a better alternative to making complex scoping rules to prevent confusing code is to
|
||||
push for a warning wave to make using `_` as an identifier a warning. Essentially, if the
|
||||
above behavior is confusing, the confusing aspects are best resolved by always using `_` as a
|
||||
discard, rather than special language rules.
|
||||
|
||||
The scoping behavior is confirmed that multiple `_`s in a lambda parameter list are discards.
|
||||
There are no other modifications to variable scopes i.e., they are not introduced in the lambda
|
||||
body scope, but they also hide nothing from the enclosing scope).
|
||||
|
||||
### Common Type Specification
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/2823
|
||||
|
||||
This is revisiting a previous design question around whether to improve the common type
|
||||
specification and also to target type various expressions. In the last discussion we wanted more
|
||||
investigation on the impacts of adding target-typing and the consequences to improving the common
|
||||
type algorithm in the future.
|
||||
|
||||
After investigation, there's a proposal to resolve questions about changing common type inference
|
||||
by looking back to an earlier rule: never infer a type that was not one of the input types. This
|
||||
is a rule that mainly comes from where to draw the line on complexity of type inference.
|
||||
|
||||
This is a simple rule, but it's arguable that there are certain enhancements that don't open up
|
||||
to question the entire space of inference, but still satisfy simple requests, like assuming that
|
||||
null and simple integer types can be inferred as nullable. This would be similar to the language
|
||||
specification for nullable lifting operations on binary operators.
|
||||
|
||||
However, that still leaves the fundamental problem we approached in the beginning: improving
|
||||
inference is a breaking change after target-typing is introduced. The proposal introduced is to
|
||||
not improve type inference in the future and consider this an acceptable outcome, given that
|
||||
target typing would satisfactorily resolve most of the examples given, and potentially in a
|
||||
clearer way than improving the common type algorithm. This would also have the property of
|
||||
preserving the original constraint of the common type algorithm, where no type is inferred that
|
||||
isn't present in any of the inputs.
|
||||
|
||||
The biggest drawback here is that the switch expression and conditional expression would behave
|
||||
differently. The conditional expression would have to preserve the common type algorithm for all
|
||||
places it succeeds, for backwards compatibility.
|
||||
|
||||
One possible way out of this is to separate the notion of common type for backwards
|
||||
compatibility, namely in overload resolution, and the inferred type, as the type used for `var`,
|
||||
where no target type is available. If feasible, this would resolve the issue mentioned at the
|
||||
previous meeting, where we would be unable to improve type inference without creating breaking
|
||||
changes in overload resolution.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Overall, we're in favor of adding target-typing for these expression forms. We should consider if
|
||||
there's anything more we would like to do to make the switch and conditional expressions more
|
||||
similar.
|
|
@ -1,118 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting Notes for Oct. 30, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Function pointer syntax
|
||||
2. Enhancing Common Type Algorithm (again)
|
||||
|
||||
## Discussion
|
||||
|
||||
### Function pointer syntax
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/2917
|
||||
|
||||
While exploring syntax for function pointers, it turns out that parsing
|
||||
the previously proposed syntax, namely
|
||||
|
||||
```C#
|
||||
func*(int, int)
|
||||
```
|
||||
|
||||
can feature exponential parsing behavior in the case of nested function
|
||||
pointers.
|
||||
|
||||
An alternative is proposed using a generic-style syntax. E.g.,
|
||||
|
||||
```C#
|
||||
func*<void>
|
||||
cdecl*<int, void>
|
||||
```
|
||||
|
||||
The proposed changes employ `*<` as being unambiguous in the language today. One suggestion is to
|
||||
use the same trick, but with a minor modification: `func` would always be the first token, and
|
||||
the calling convention would be the second token e.g.,
|
||||
|
||||
```C#
|
||||
func cdecl*<int, void>
|
||||
```
|
||||
|
||||
and the set of calling conventions would evolve as the set of CLR-supported
|
||||
calling conventions evolve.
|
||||
|
||||
There are a couple features which aren't mentioned, and we're not sure if we
|
||||
need to support them, and how we would do so.
|
||||
|
||||
Mainly, we don't know whether certain features, like attributes, require definitions of named
|
||||
function pointers, instead of anonymous function pointer types. The runtime has proposed using
|
||||
attributes to express configuration, like suppressing native/managed transition.
|
||||
|
||||
The current syntax doesn't have a place to put attributes, so if we wanted to support that
|
||||
design, we would have to have some definition to place attributes. This would be similar to
|
||||
delegate definitions, where you can define a delegate with a name and attributes, and then refer
|
||||
to the delegate type by name.
|
||||
|
||||
However, the current runtime implementation doesn't support declaring tokens for the function
|
||||
pointer, so there's no official mechanism to attach attributes to a function pointer. A follow-up
|
||||
question is to ensure that the spec allows us to put the calling convention in the signature
|
||||
during emit as part of the function pointer type.
|
||||
|
||||
We brainstormed a bunch of possible syntaxes (some more serious than others):
|
||||
|
||||
```C#
|
||||
delegate managed *int(int, long)
|
||||
|
||||
delegate managed *List<T>(int, long)
|
||||
|
||||
delegate * List<T> managed(int, long)
|
||||
|
||||
delegate<managed> * List<T>(int, long)
|
||||
|
||||
delegate * managed List<T>(int, long)
|
||||
|
||||
delegate managed int *(int, long)
|
||||
|
||||
delegate managed*<int, long, void>
|
||||
|
||||
delegate *<int, long, void>
|
||||
|
||||
delegate* managed<int, long, void>
|
||||
delegate*<int, long, void>
|
||||
```
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The new syntax is agreed to be unambiguous, as far as we can see. We agree on three
|
||||
modifications:
|
||||
|
||||
1. The optional calling convention should always have a prefix.
|
||||
2. Putting the `*` near the prefix is better, as it makes the function pointer
|
||||
more recognizable.
|
||||
3. `delegate` is a better prefix, as it is already a keyword.
|
||||
|
||||
Our final decision is
|
||||
|
||||
```C#
|
||||
'delegate*' optional_calling_convention '<' type < type , ... > , return_type '>'
|
||||
```
|
||||
|
||||
### Enhancing Common Type Specification follow-up
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/2823
|
||||
|
||||
The previous proposal was to add target-typing as a fallback to existing features,
|
||||
like the conditional expression, and do no more work on enhancing the common type
|
||||
algorithm.
|
||||
|
||||
This would solve certain problems and not others. In the case where the conditional expression
|
||||
has a common type, but that type is not the one you want (e.g., `b ? 1 : 2`, but you wanted the
|
||||
result to be a `byte`), target typing would not solve this.
|
||||
|
||||
At the same time, not having all possible improvements doesn't seem to impact the
|
||||
decision for nullable, specifically. We already have special language knowledge
|
||||
and handling of nullable and when language algorithms fail to introspect and find
|
||||
the "obvious" solution, this feels worse than more general failures.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
None today. We ran out of time and would like to talk about it more.
|
|
@ -1,161 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Nov. 11, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Confirm removal of warning calling a method that returns `[MaybeNull]T`
|
||||
2. Allow interpolated string constant
|
||||
3. Enhancing the Common Type Specification
|
||||
4. Type pattern
|
||||
5. Simple name binding with target types
|
||||
|
||||
## Discussion
|
||||
|
||||
### Calling a method that returns `[MaybeNull]T`
|
||||
|
||||
We previously proposed this as a safety warning, since the value of `T` could be nullable,
|
||||
similar to `default(T)`. The approach of warning on all uses turns out to be more annoying than
|
||||
we thought, producing multiple false positives.
|
||||
|
||||
There are a couple steps to improving it. First, we'd like to not warn if the expression is
|
||||
returned in a method that is also annotated to return `[MaybeNullT]`. This would require us to
|
||||
use the information from the nullable attributes (or at least `[MaybeNull]`) inside the method.
|
||||
We also have to implement a three flow state design for nullable analysis, since it's not good
|
||||
enough to know whether the flow state is maybe null or not null (since all unconstrained generics
|
||||
are already considered maybe null), we need to know if the state is maybe null, even if the
|
||||
substituted generic does not allow null.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Confirmed that the warning can be removed. This will make it likely that we will produce more
|
||||
warnings in the case that all of these component parts are working, but we're hoping that almost
|
||||
all of these warnings will be real safety issues, not false positives.
|
||||
|
||||
### Interpolated string constant
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2951
|
||||
|
||||
The simplest form of this proposal is to just allow constant strings with `$` in front of them.
|
||||
For example,
|
||||
|
||||
```C#
|
||||
const string s = $"abc";
|
||||
```
|
||||
|
||||
This doesn't really seem very useful, except in refactoring where you had an interpolated string,
|
||||
and made it constant, and it happened to not have any substitution.
|
||||
|
||||
A more useful version would allow constant interpolated strings if all of the substitutions are
|
||||
constant strings.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Seems reasonable. We'll put it in "any time". @agocke to champion.
|
||||
|
||||
### Enhancing the Common Type Specification (again)
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2823
|
||||
|
||||
We discussed a number of improvements, notably using target-typing as a fallback when there is no
|
||||
common type. The idea to improve target-typing for nullable seems interesting, but we need a more
|
||||
detailed proposal to examine. There's a worry that this will significantly complicate type
|
||||
inference, which needs investigation.
|
||||
|
||||
There are a few associated questions, namely:
|
||||
|
||||
1. Do we improve the common type algorithm to not find a common type if all the component
|
||||
types cannot be converted to it? (e.g., `1` and `null` would have no common type)
|
||||
|
||||
2. Do we allow improve common type inference for the cases where there is no target type (e.g.,
|
||||
when assigned to `var` or a method that takes a generic `T`)
|
||||
|
||||
3. Do we allow target typing when the common type is not convertible to the target? (e.g.,
|
||||
`byte b = cond ? 1 : 2` would fall back to target typing because `int` is not convertible to
|
||||
`byte`)
|
||||
|
||||
There's also the behavioral difference between the old expressions which have target-typing as a
|
||||
fallback, and the switch expression, which currently prefers the target-typing if one exists.
|
||||
Some of the questions above, including (3), would make the two behaviors be very similar, in that
|
||||
target-typing would be present in the few cases that rely on it.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We agree to the following changes:
|
||||
|
||||
1. Target-typing as a fallback for the old expressions (array creation, conditional expression, et al.)
|
||||
|
||||
2. We want target-typing to succeed in the case where the common type does not convert to the
|
||||
target-type
|
||||
|
||||
3. We want to change switch expression to use the natural type if it converts to the target-type,
|
||||
and only use target-typing if the natural type does not convert to the target.
|
||||
|
||||
We want to continue to investigate improvements to the common type algorithm, including changes
|
||||
to support nullability.
|
||||
|
||||
### Type patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2925
|
||||
|
||||
The first proposal is to simplify some existing constructs and add some simplification to some
|
||||
current behavior by creating a simple "type pattern" without a variable introduction.
|
||||
|
||||
```C#
|
||||
void M(object o1, object o2)
|
||||
{
|
||||
var t = (o1, o2);
|
||||
if (t is string) // This is currently legal and we would just change the spec to say that this
|
||||
// is a type pattern, although the `is` would prefer a type in an ambiguity
|
||||
// to preserve backwards compatibility
|
||||
if (t is (int, string)) // This would now be legal as a tuple pattern, containing two type patterns
|
||||
switch (t)
|
||||
{
|
||||
case string: // this would now work, but would be the inverse of `is`, preferring a constant
|
||||
// for backwards compatibility
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This has broad agreement as a good change, and multiple LDM members have been frustrated that the
|
||||
switch statement and expressions often require a `_` in these places.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The first proposal, allowing simple type patterns, is broadly useful right now. Accepted for the
|
||||
C# 9.0 milestone.
|
||||
|
||||
### Name lookup for binding simple names with known target type
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2926
|
||||
|
||||
The second proposal is to adjust binding with a known target type, so you could say
|
||||
|
||||
```C#
|
||||
class Outer
|
||||
{
|
||||
public class Inner1 : Outer {}
|
||||
public class Inner2<T> : Outer {}
|
||||
}
|
||||
|
||||
int M1(Outer o) => o switch
|
||||
{
|
||||
Inner1 _ => 0, // Binds to Outer
|
||||
Inner2<int> _ => 1;
|
||||
}
|
||||
```
|
||||
|
||||
or even
|
||||
|
||||
```C#
|
||||
Color x = b ? Red : Greed;
|
||||
var y = b ? Color.Red : Greed;
|
||||
```
|
||||
|
||||
**Conclusion**
|
||||
|
||||
This change feels especially natural in switch statements and expression with enums. It seems
|
||||
useful in the other contexts as well. The extension to nested classes would be particularly
|
||||
relevant to a discriminated union based off of nested classes.
|
||||
|
||||
There's a lot here in the binding changes. Let's sit on it a bit and take a look with a
|
||||
discriminated unions proposal. Triaged to C# 9.0 to match discriminated unions.
|
|
@ -1,114 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Nov. 13th, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Discriminated unions
|
||||
2. Improved analysis of `[MaybeNull]T`
|
||||
|
||||
## Discussion
|
||||
|
||||
### Initial discriminated union proposal
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/discriminated-unions.md
|
||||
|
||||
Questions around some restrictions:
|
||||
|
||||
1. Why can't you have constructor parameters on the enum class type?
|
||||
- No real reason, except worries about inheritance aside from members
|
||||
- Keeping inheritance syntactically close by is considered a feature of the proposal,
|
||||
since the user can easy analyze what all the possible children of the root are
|
||||
- Syntax for the base call in value members would have to be specified
|
||||
|
||||
2. How `partial` do we want to allow things? There's good reason to put all the members together
|
||||
|
||||
3. Are records short enough syntax that we don't need the "simple" type syntax?
|
||||
|
||||
- Regardless of brevity, if members are required to explicitly inherit from the root this seems
|
||||
like purely useless work, since doing otherwise would be an error
|
||||
|
||||
4. What if you want to define just regular nested classes, that don't inherit from the root type?
|
||||
|
||||
5. Scoping for nested enum class members? The proposal for altering binding wouldn't help with
|
||||
nested members, because it only applies to simple names.
|
||||
|
||||
```C#
|
||||
enum class Expr
|
||||
{
|
||||
enum class BinaryOperator
|
||||
{
|
||||
Add(Expr left, Expr right),
|
||||
Multiply(Expr left, Expr right)
|
||||
}
|
||||
}
|
||||
|
||||
Expr M(Expr left, Expr right)
|
||||
{
|
||||
return Add(left, right); // error, no name "Add" found under Expr
|
||||
}
|
||||
|
||||
int M(Expr e) => e switch
|
||||
{
|
||||
Add _ => 0, // error, no name Add found under Expr
|
||||
}
|
||||
```
|
||||
|
||||
There's also a proposed alternate syntax that would let you put everything directly into the enum
|
||||
class:
|
||||
|
||||
```C#
|
||||
enum class Color
|
||||
{
|
||||
Red,
|
||||
Greed; // semicolon delimiting end of enum class cases, and start of regular members
|
||||
|
||||
public static int HelperField;
|
||||
public static int HelperProperty => 0;
|
||||
public int HelperMethod() => 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Improved analysis of `[MaybeNull]T`
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/2946
|
||||
|
||||
The new proposed abstract domain for nullable analysis would be:
|
||||
|
||||
```C#
|
||||
enum
|
||||
{
|
||||
NotNull,
|
||||
MaybeNull,
|
||||
MaybeNullEvenIfNotNullable
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
If we improve analysis of `MaybeNull` inside the method, do we want to push the new functionality
|
||||
through for all the nullable attributes at the same time? This seems useful from a consumer
|
||||
perspective, in that you get the different warnings all at the same time, but there's also the
|
||||
simple question of resources and scheduling.
|
||||
|
||||
Similarly, if we do improve `MaybeNull` analysis in the body of the method, do we need to improve
|
||||
the analysis in other places, like overriding, at the same time? Or are we OK with a piecemeal state
|
||||
where `MaybeNull` is enforced in the method body but not in overriding? Or where we enforce `MaybeNull`
|
||||
in overriding, but not other attributes like `DisallowNull`?
|
||||
|
||||
A broader question is how common `MaybeNull` is in general. Our experience in the .NET Core mscorlib
|
||||
is that there are only 10 occurrences of `MaybeNull`, and even when broadening to LINQ, there are at
|
||||
most a few dozen occurrences, mostly in places that may return a `default` value. In comparison,
|
||||
`NotNullIfNotNull` has ~30 occurrences in mscorlib.
|
||||
|
||||
On the other hand, `default(T)` is very common and is an instance of `MaybeNull` in a sense. This implies
|
||||
that the flow state of `MaybeNull` is particularly important.
|
||||
|
||||
An additional question is whether or not to revive the `T?` proposal to deal with the historical problems
|
||||
around `MaybeNull` annotations on locals and other places where attributes are not permitted.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We think improving any aspect of the attributes can be considered independently and we will not
|
||||
require the changes to ship together. We think that proposal is good. Accepted.
|
||||
|
||||
No statement on `T?`. We will schedule a discussion soon to get a permanent decision on the proposal.
|
|
@ -1,73 +0,0 @@
|
|||
|
||||
# C# LDM for Nov. 18th, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. More patterns
|
||||
|
||||
## Discussion
|
||||
|
||||
### More patterns
|
||||
|
||||
Parenthesized patterns:
|
||||
|
||||
- What about ambiguity with a potential 1-tuple pattern?
|
||||
|
||||
- There was a previous ambiguity here with a parenthesized constant expression,
|
||||
so matching a 1-tuple pattern requires resolving ambiguity, for example by adding
|
||||
tuple names: `x is (Item1: var tupleVal)`
|
||||
|
||||
Relational patterns:
|
||||
|
||||
We don't love the syntax. The problem is really that we're using a binary operator in
|
||||
a unary context. On the other hand, it's useful in that it's easy to adjust for closed
|
||||
or open bounds by adjusting the inclusiveness of the operator.
|
||||
|
||||
The other shaky part of the syntax is support for user-defined operators. One of the
|
||||
fundamental parts of pattern matching is that the semantics are well-understood, meaning
|
||||
that the switch expression expects certain things must be true, like that checks must return
|
||||
the same value for the same inputs and can be called any number of times. We have a few patterns
|
||||
that execute user-defined code, but it seems more likely that they're usually safe (property
|
||||
getters and Deconstruct).
|
||||
|
||||
For `==` and `!=` specifically, they seem redundant and likely to cause confusion when there is a
|
||||
user-defined operator that is not supported. Consensus is to remove support in relational patterns.
|
||||
|
||||
The other confusing part is conversion, where the input type does not statically contain a built-in
|
||||
the binary operator. For instance,
|
||||
|
||||
```C#
|
||||
object o = (short)1;
|
||||
_ = o switch
|
||||
{
|
||||
< 3 => 0, // This would be false, since `o` is a short, not an int, and the
|
||||
// unconverted '3' is an `int`
|
||||
};
|
||||
```
|
||||
|
||||
This is already how constant patterns work, so there may be some confusion here already, but we're
|
||||
worried about broadening the problem by adding the `not` pattern in combination with the relational
|
||||
operators. For example, if you match `not < 3`, this would evaluate to true, but not because the
|
||||
value is not less than three, but because it is not an int. This would mean that `>= 3` and `not < 3`
|
||||
would be different, since the type check can play into the check.
|
||||
|
||||
However, we don't have any better approaches, and the hope is that this will be a relatively rare
|
||||
occurrence. If the input pattern has a statically known built-in operator this would not be a concern.
|
||||
|
||||
The proposal states that the input type of the expression must contain a conversion that is not a boxing
|
||||
conversion. However, we do support constant pattern checks for unconstrained type parameters, so we
|
||||
need to change the proposal to allow unconstrained type parameters as well.
|
||||
|
||||
### Pattern combinators
|
||||
|
||||
The proposal contains three breaking changes:
|
||||
|
||||
- `not` is considered a type in C# 8 and a pattern in C# 9.
|
||||
|
||||
- `and` and `or` are allowed as variable names in C# 8, but are pattern combinators in C# 9
|
||||
|
||||
An important question is whether these are allowed and how they are breaking. Changing behavior is
|
||||
probably a bridge too far. We would consider providing an error in the old scenarios and forcing
|
||||
disambiguation.
|
||||
|
||||
We'll need more discussion on the breaking changes and how we can mitigate them.
|
|
@ -1,126 +0,0 @@
|
|||
# C# Language Design Notes for Nov 25, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Allow `T?` where `T` is a type parameter that's not constrained to be a value or reference type
|
||||
|
||||
# "Unconstrained" `T?`
|
||||
|
||||
In C# 8.0 we entertained the possibility of allowing `T?` on type parameters `T` that are unconstrained, or constrained only in a way that they can still be instantiated with both value and reference types.
|
||||
|
||||
The idea was to accurately express the type of `default(T)`. To this end, `T?` would end up being the same as `T`, except when `T` is instantiated with a nonnullable reference type `C`, in which case `T?` is `C?`. The reason is that for non-nullable reference types `C`, `default(C)` is still null, and hence of the type `C?`, not `C`.
|
||||
|
||||
`T?` would, for instance, allow the return type of methods like `FirstOrDefault()` to be better expressed:
|
||||
|
||||
``` c#
|
||||
public T? FirstOrDefault<T>(this IEnumerable<T> src);
|
||||
```
|
||||
|
||||
In the generic context where `T` is in scope, the meaning of `T?` would be "may be null, even when `T` is not nullable". The expression `default(T)` would no longer warn, but the type of it would be `T?`, and assigning `T?` to `T` *would* warn.
|
||||
|
||||
In the end we decided not to allow `T?` due to a couple of problems which we'll get back to below. Instead we addressed many of the scenarios with the addition of nullable attributes such as `[MaybeNull]`, which can be used to annotate e.g. the `FirstOrDefault()` method:
|
||||
|
||||
``` c#
|
||||
public [return:MaybeNull] T FirstOrDefault<T>(this IEnumerable<T> src);
|
||||
```
|
||||
|
||||
## Recent changes
|
||||
|
||||
Default values keep causing grief, and we've recently [(Nov 13)](https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-11-13.md#improved-analysis-of-maybenullt) decided on some changes to handle them:
|
||||
|
||||
1. Add a third null-state next to "not null" and "maybe null", which is "maybe null *even* when the type doesn't allow", and make that the null state of `default(T)` for an unconstrained type parameter `T`, as well as results of method calls etc. (such as `FirstOrDefault`) that were annotated with `[MaybeNull]`
|
||||
2. Remove the "W warning" on assignment of such values to locals of type `T`, but instead subsequently track the new state *for* those locals, which may subsequently lead to warnings if used unsafely
|
||||
3. Remove warnings when producing such values as a result (e.g. return value) that is itself annotated as `[MaybeNull]`
|
||||
|
||||
Put together, these three eliminate most of the friction around the use of `default(T)` and `[MaybeNull]`. However, they also get very close to the previously proposed semantics of allowing `T?` on unconstrained `T`. We could take this two ways:
|
||||
|
||||
1. Since we mostly succeeded in addressing the problem without `T?`, it further reduces the need for it.
|
||||
2. This shows that "the type of `default(T)`" is an important concept that should be properly reified in the language instead of tricks with attributes.
|
||||
|
||||
## Taking another look at "unconstrained" `T?`
|
||||
|
||||
The main arguments in favor of a syntax for "the type of `default(T)`" even with the above changes are:
|
||||
|
||||
1. By adding "it" as a tracked null state, we're already half embracing it, but by leaving it inexpressible as a type we need to fall back on "tricks" to make use of it
|
||||
2. `[MaybeNull]` doesn't help when "the type of `default(T)`" is needed in a constructed (array, generic, tuple, ...) type
|
||||
|
||||
The use of the syntax `T?` to mean "the type of `default(T)`" as two significant problems as well, which ultimately led us to not embrace it for C# 8.0:
|
||||
|
||||
1. Its meaning is different from the meaning of `T?` when `T` is constrained to be a value type.
|
||||
2. Overrides cannot restate their constraint, and `T?` in those today always means the value type kind.
|
||||
|
||||
## Problem 1: `T?` and `T?` mean different things
|
||||
|
||||
The first problem is not technical, but one of perception and language regularity. Consider:
|
||||
|
||||
```
|
||||
public T? M1<T>(T t) where T : struct;
|
||||
public T? M2<T>(T t);
|
||||
|
||||
var i1 = M1(7); // i1 is int?
|
||||
var i2 = M2(7); // i2 is int
|
||||
```
|
||||
|
||||
The declaration of `M1` is legal today. Because `T` is constrained to a (nonnullable) value type, `T?` is known to be a nullable value type, and hence, when instantiated with `int`, the return type is `int?`.
|
||||
|
||||
The declaration of `M2` is what's proposed to allow. Because `T` is unconstrained, `T?` is "the type of `default(T)`". When instantiated with `int` the type of `default(int)` is `int`, so that is the return type.
|
||||
|
||||
In other words, for the same provided `T` these two methods have *different* return types, even though the only difference is that one has a constraint on `T` but the other does not.
|
||||
|
||||
The cognitive dissonance here was a major part of why we didn't embrace `T?` for unconstrained `T`.
|
||||
|
||||
## Problem 2: pseudo-"unconstraint" in overrides for disambiguation
|
||||
|
||||
In C# overrides of generic methods cannot re-specify constraints, but must inherit them from the original declaration. Before C# 8.0, when `T?` appeared in signatures of such overrides, it was always assumed to mean the nullable value type `Nullable<T>`, because what else could it mean?
|
||||
|
||||
In C# 8.0 `T?` acquired the possible second meaning of nullable reference type. In order to disambiguate the search for the original declaration, we reluctantly introduced the ability to specify "pseudo-constraints" on the overrides: When `T` was a type parameter constrained to be a reference type, you can now say so by adding `where T: class` to the declaration. If you want the default assumption of it being constrained to a value type to be made explicit, you can also optionally specify `where T: struct`.
|
||||
|
||||
These helper constraints are only there to help find the right declaring method up the inheritance chain (which may be overloaded). The *actual* constraint that's emitted into generated code is still the one that's inherited from the original declaration, once that one has been identified. Thus, only `class` and `struct` can be used as constraints on an override.
|
||||
|
||||
Now here comes a third `T?`, the defining characteristic of which is that it is *not* constrained to be either a reference or value type. To distinguish it from the other two in an override of a generic method, we would need a third "constraint" that is actually an *unconstraint* - that specifically says that it is *not* constrained!
|
||||
|
||||
## Solutions
|
||||
|
||||
We have two general approaches to address these problems (beyond the ever-present solution of giving up on the feature):
|
||||
|
||||
1. Live with problem 1, and try to explain things as best we can. Find a syntax to express the "unconstraint" of problem 2.
|
||||
2. Use a different syntax than `T?` to express "the type of `default(T)`".
|
||||
|
||||
## Solution 1
|
||||
|
||||
A brain storm for solution 1 syntaxes yielded:
|
||||
|
||||
``` c#
|
||||
override void M1<[Unconstrained]T,U>(T? x) // a
|
||||
override void M1<T,U>(T? x) where T: object? // b
|
||||
override void M1<T,U>(T? x) where T: unconstrained // c
|
||||
override void M1<T,U>(T? x) where T: // d
|
||||
override void M1<T,U>(T? x) where T: ? // e
|
||||
override void M1<T,U>(T? x) where T: null // f
|
||||
override void M1<T,U>(T? x) where T: class|struct // g
|
||||
override void M1<T,U>(T? x) where T: class or struct // h
|
||||
override void M1<T,U>(T? x) where T: cluct // joke
|
||||
```
|
||||
|
||||
Of these we have a vague preference for c, expressing explicitly that there is *not* a constraint, closely followed by b, which is the most general constraint one could state. None of the others resonated.
|
||||
|
||||
## Solution 2
|
||||
|
||||
A brain storm for solution 2 syntaxes yielded:
|
||||
|
||||
``` c#
|
||||
void M1<T,U>(default(T) x) // X
|
||||
void M1<T,U>(default<T> x) // Y
|
||||
void M1<T,U>(T?? x) // Z
|
||||
```
|
||||
|
||||
Solutions X and Y try to be explicit about the type meaning "the type of `default(T)`", but run the risk of implying "the result *is* `default(T)`". Solution Z is mostly just the shortest `?`-like token we could think of that isn't `?`.
|
||||
|
||||
We unanimously preferred Z. `??` can be read as *may be* (`?`) nullable (`?`).
|
||||
|
||||
## Conclusion
|
||||
|
||||
Comparing the two approaches we do favor solution 2, adopting the syntax `T??` to mean "the type of `default(T)`" (when `T` is unconstrained). We do think that this is the best solution, because it addresses both problems, is terse, and looks reasonable. We will move ahead with prototyping and fleshing it out.
|
||||
|
||||
We would probably only allow it to be used at all on type parameters `T` that are not constrained to be either value or reference types. That way you can never use either `??` or `?` on the same thing.
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Dec. 11, 2019
|
||||
|
||||
1. Design review feedback
|
||||
|
||||
## Discussion
|
||||
|
||||
We got feedback from the design review that we shouldn't try to conflate too many problems. If
|
||||
we want to make it easier to support structural equality, we should see if it's possible to
|
||||
address directly, without requiring the other features of records. One suggestion was to take
|
||||
inspiration from `VB`, which allows the `key` modifier to be added to VB anonymous types to
|
||||
indicate structural equality with the members used as the keys.
|
||||
|
||||
We took that advice and looked at a sketch of what that could look like:
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public key string Item1 { get; }
|
||||
public string Item2 { get; }
|
||||
}
|
||||
```
|
||||
|
||||
The `key` modifier would be used to control generated equality, such that all members marked
|
||||
`key` would be compared for equality in an `Equals` override (using the same pattern as in the
|
||||
original records proposal).
|
||||
|
||||
The above code sample certainly looks simple, but unfortunately it's not sufficient for
|
||||
real-world code. Both `Item1` and `Item2` are `get`-only autoproperties, meaning that as-is there
|
||||
is no way to initialize those members. A working example looks more like:
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public key string Item1 { get; }
|
||||
public string Item2 { get; }
|
||||
public C(string item1, string item2)
|
||||
{
|
||||
Item1 = item1;
|
||||
Item2 = item2;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is significantly longer than the original sample, grows with the number of properties, and
|
||||
is repetitive. The latter is particularly problematic, as repetitive boilerplate is often a source
|
||||
of hard-to-see bugs or typos.
|
||||
|
||||
Worse, we've seen that when construction becomes laborious, users resort to making their types
|
||||
mutable instead of writing out the constructor, e.g.
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public key string Item1 { get; set; }
|
||||
public string Item2 { get; }
|
||||
}
|
||||
```
|
||||
|
||||
This is unfortunate in most code, but it's worrying when combined with structural equality. When
|
||||
used in Dictionaries, mutable types with structural equality are an anti-pattern because the hash
|
||||
code of the type changes, causing the type to "disappear" from the Dictionary. It's one thing if
|
||||
the user opts-in to this risk, knowing that they need to be careful, and a completely different
|
||||
situation if the language encourages a dangerous pattern.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
This is an interesting design point that we think we'll incorporate. We've also agreed that we
|
||||
have a hard design requirement: if we provide a feature for easy structural equality, we must
|
||||
provide a more convenient syntax for constructing immutable types in the same release. To do
|
||||
otherwise would be to effectively make a trap for users.
|
||||
|
||||
## Nominal vs Positional
|
||||
|
||||
We also continued to explore the space of nominal vs. positional records. An insight from the
|
||||
previous discussion is that nominal vs. positional records are really about improving syntax
|
||||
for type construction. Positional records are about taking the existing type construction,
|
||||
constructors, and giving them a shorter syntax. Nominal records are about identifying some
|
||||
weaknesses of the existing construction system and providing a new feature to support them. In
|
||||
both cases, though, the proposed features shorten construction by avoiding repeating the member
|
||||
declarations and the assignments.
|
||||
|
||||
We also had the following notes, in no particular order:
|
||||
|
||||
* For positional constructors, it's important to consider primary constructors. We have one
|
||||
proposal for primary constructors, but have not had a discussion on whether we want them, and if
|
||||
the syntax is worth taking away from the record syntax.
|
||||
|
||||
* If the `initonly` keyword is required, even for common cases, this is about
|
||||
as expensive syntax-wise as a setter. It may be a few more characters, but it
|
||||
keeps things on one line, as opposed to the multi-line constructors that are
|
||||
required right now.
|
||||
|
||||
* There's an opposition between evolving existing data types and picking and choosing features when
|
||||
you need them, but also providing a simple syntax for the most common case. Certainly positional
|
||||
records solve a common case. The question is how common that case is.
|
||||
|
||||
* Positional records use existing initialization strategy (construction) which is fairly
|
||||
well-understood. The initonly feature, by contrast, will force us to reexamine some assumptions.
|
||||
For instance, constructors take all inputs at once, meaning that you can enforce requirements
|
||||
between them at construction time. `initonly` overrides properties after construction, and
|
||||
one-by-one, so it's not possible, or not obvious, how to provide this functionality.
|
||||
|
||||
* There are mixed feelings on the requirements of what features will be available individually, and
|
||||
which are separable. Some people feel that addressing the most common case is sufficient for
|
||||
providing the value of the feature, namely that there can be a single "record" which provides
|
||||
immutable construction and value equality, and that is the only way to access these features.
|
||||
Others think that the features need to be adoptable independently: existing types need to be able
|
||||
to adopt generated equality without adopting immutable construction, while immutable construction
|
||||
needs to be available without structural equality.
|
||||
|
||||
One conclusion is that no proposal will solve all record-related problems. This is fine. We also
|
||||
have general agreement that there should be a convenient shorthand for the combination of most or
|
||||
all of the features (simple immutable construction, structural equality, etc.). Notably we don't
|
||||
all agree on whether or not structural equality will be the most common type of equality for
|
||||
records, but the shortest record form may include structural equality for other language design
|
||||
reasons.
|
|
@ -1,122 +0,0 @@
|
|||
|
||||
# C# Language Design Notes for Dec. 16, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Switch expression as a statement expression
|
||||
2. Triage
|
||||
|
||||
## Discussion
|
||||
|
||||
### Switch expression as a statement expression
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2860
|
||||
|
||||
The proposal being discussed is whether to allow the switch expression without a discard:
|
||||
|
||||
```C#
|
||||
_ = a switch
|
||||
{
|
||||
...
|
||||
};
|
||||
// becomes
|
||||
a switch
|
||||
{
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
One of the against is that it makes for a confusing decision in the language as to whether you
|
||||
use a switch expression or switch statement. Right now the guidance is simple: if you are in a
|
||||
statement context, use a statement. If you have an expression context, use a switch expression.
|
||||
Now, for a statement, you could use either a switch statement, or a switch expression in
|
||||
statement form. It's not clear which.
|
||||
|
||||
One way of resolving this is that these are two parallel features that provide similar features
|
||||
in a slightly different syntax and semantics. Then the answer simply becomes, use whichever one
|
||||
you like better. If the new switch expression form includes exhaustiveness checking, that would
|
||||
be a reason to use or not to use it, aside from the syntax differences. Similarly, the switch
|
||||
statement ability to `goto` another case is a reason to use that form. However, if we accept
|
||||
that, the switch expression feels artificially limited. To provide a satisfactory parallel
|
||||
feature we have to augment the switch expression to allow for statements in the switch arms. Then
|
||||
there is a potential new set of features: block in switch expressions.
|
||||
|
||||
On the other hand, this feels like feature creep. The original proposal was quite simple: allow
|
||||
users to elide `_ =` and remove the requirements for the arms to have a common type. While we may
|
||||
want to have a number of different new features for switch expression to make it comparable to
|
||||
the switch statement, there's value in doing the feature as-is, and adding those features later.
|
||||
This is contingent on us being fairly confident that the new features can be added without
|
||||
breaking changes, but there's a fair amount of confidence that we know where we would go with the
|
||||
feature. This perspective would require us to keep certain behaviors to ensure that the switch
|
||||
expression keeps its differences from the switch statement. For instance, the new switch
|
||||
expression-as-statement would have to check exhaustiveness if we see it as a strict improvement
|
||||
for the switch expression.
|
||||
|
||||
Lastly, we all find the proposed switch expression-as-statement requiring a semicolon i.e.,
|
||||
|
||||
```C#
|
||||
a switch
|
||||
{
|
||||
b => ...,
|
||||
c => ...
|
||||
}; // semicolon required
|
||||
```
|
||||
|
||||
as being extremely ugly.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Rejected as-is. We'd be interested in a new proposal on this topic, addressing many of the
|
||||
concerns that we brought up today.
|
||||
|
||||
### Triage
|
||||
|
||||
#### Definite assignment of private reference fields
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Accepted for warning waves v1, wherever that is triaged.
|
||||
|
||||
#### Remove restriction on yielding a value in the body of a try block
|
||||
|
||||
Also for async iterators.
|
||||
|
||||
Issue #2949
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Accepted, Any Time.
|
||||
|
||||
#### Generic user-defined operators
|
||||
|
||||
Issue #813
|
||||
|
||||
**Conclusion**
|
||||
|
||||
There's no syntax in the invocation to specify the type arguments, in case inference doesn't
|
||||
succeed, and we think almost any syntax in the invocation location would be ugly. In addition, we
|
||||
don't have a lot of examples of why this would be significantly better than alternatives (like
|
||||
writing a method).
|
||||
|
||||
Rejected.
|
||||
|
||||
#### Support for method argument names in `nameof`
|
||||
|
||||
Issue #373
|
||||
|
||||
It looks like there's a significant breaking change if we allow the parameter names to be in
|
||||
scope generally.
|
||||
|
||||
```C#
|
||||
const int p = 3;
|
||||
[Attribute(Property = p)]
|
||||
void M(int p) { }
|
||||
```
|
||||
|
||||
If we just allow `nameof` to have special scoping to allow the names in the method declaration to
|
||||
be in scope, then there's no language breaking change. The scoping rules would prefer names in
|
||||
the method header (including type parameters) over rules in the rest of the program.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Accepted, Any Time.
|
|
@ -1,147 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Dec. 18, 2019
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Nullable issues
|
||||
|
||||
a. Pure null checks
|
||||
|
||||
b. Consider `var?`
|
||||
|
||||
## Discussion
|
||||
|
||||
### Pure null checks
|
||||
|
||||
We have the following syntax which semantically check for null in the language:
|
||||
|
||||
* `e is null`
|
||||
* `e == null`
|
||||
* `e is {}`
|
||||
* `e is object`
|
||||
|
||||
However, for nullability we have a separate notion of a "pure" null check that
|
||||
causes a null branch to appear, even if the variable being tested was declared
|
||||
not null, or vice versa.
|
||||
|
||||
An example of why this would matter is
|
||||
|
||||
```C#
|
||||
var current = myLinkedList.Head; // assume Head is nullable oblivious/unannotated
|
||||
while (current is object)
|
||||
{
|
||||
...
|
||||
current = current.Next; // assume oblivious
|
||||
}
|
||||
current.ToString(); // only warns if `is object` is a pure null test
|
||||
```
|
||||
|
||||
We previously established that all "pure" null checks contain the word `null` in them, meaning
|
||||
that only `e is null` and `e == null` are pure null checks today. There is a proposal that we
|
||||
should unify all forms that are semantically equivalent, regardless of syntax.
|
||||
|
||||
There is also a proposal to expend this even to places which are not "pure" checks, i.e.
|
||||
they have semantic effect larger than just checking for null. For instance, we could
|
||||
also check `e is object o`, which also introduces a variable `o`. We came up with
|
||||
the following list of potential checks:
|
||||
|
||||
```
|
||||
e is null // pure
|
||||
|
||||
e is object // Proposed
|
||||
e is [System.]Object // Proposed
|
||||
e is object _
|
||||
e is object x
|
||||
|
||||
s is string // s denotes expr of static type string
|
||||
s is string x
|
||||
o is string x
|
||||
|
||||
e is {} // Proposed
|
||||
e is {} _
|
||||
e is {} x
|
||||
|
||||
e == null // pure
|
||||
e != null // pure
|
||||
|
||||
e is not null // pure
|
||||
e is not object // etc...
|
||||
```
|
||||
|
||||
All parties argue that other positions are confusing as to why something is a pure
|
||||
null check and something else, that's very similar, is not. It seems like drawing
|
||||
any particular line will always imply that something similar could be confusing.
|
||||
|
||||
One difference between versions that check between a semantically pure null check, i.e. a piece
|
||||
of syntax that has no other meaning than testing for null, is that if there is a pure null check
|
||||
then any warning is definitely a bug in user code: either the check is superfluous, or there is
|
||||
an actual safety issue. If the check is not pure, there may not be a bug, because the check may
|
||||
not actually be superfluous and this may be a spurious warning.
|
||||
|
||||
Given that the pure null checking is useful, it's mainly about finding the right balance between
|
||||
helping the user find bugs in their code and finding a set of rules that are also easily
|
||||
understandable. The main argument against broadening beyond our current rule is that "pure checks
|
||||
contain the word 'null'" is a simple rule, and adding warnings in an update is a heavy way to
|
||||
address the issue.
|
||||
|
||||
On the other hand, we have changed nullable warnings multiple times already, plan to
|
||||
do it again, and have warned people that nullable warnings may be in flux for a time.
|
||||
If the feature is also meant to react to user intent, and if we believe `x is object`
|
||||
is intended by the user to be a null check, then making it a pure null check would
|
||||
be correctly responding to user intent.
|
||||
|
||||
We could also decide based on whether or not we want to suppress certain patterns. If
|
||||
we believe `x is object` or `x is {}` aren't good ways to test for null, then making
|
||||
them not pure null checks would encourage users not to use it. This did not seem a
|
||||
compelling position for anyone in LDM.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We agree that we should broaden the set of pure null checks. We agree that `x is object` should
|
||||
be a pure null check. Moreover this should be based on the type in the `is` expression, meaning
|
||||
that any type `T` that binds to `System.Object` in `x is T` would be a pure null check. We also
|
||||
agree that `x is {}` is a pure null check.
|
||||
|
||||
None of `x is object _`, `x is object o`, `x is {} _`, or `x is {} o` are pure null checks.
|
||||
|
||||
### `var?`
|
||||
|
||||
At this point we've seen a large amount of code that requires people spell out the
|
||||
type instead of using var, because code may assign `null` later.
|
||||
|
||||
An example,
|
||||
|
||||
```C#
|
||||
var current = myLinkedList.Head; // annotated not null
|
||||
while (current is object)
|
||||
{
|
||||
...
|
||||
current = current.Next; // warning, Next is annotated nullable, but current is non-null
|
||||
}
|
||||
```
|
||||
|
||||
One way to deal with this is to allow `var?`,
|
||||
|
||||
```C#
|
||||
var? current = myLinkedList.Head;
|
||||
// now current is nullable, but the flow state is non-null
|
||||
current.ToString(); // no warning, because the flow analysis says it's not null
|
||||
```
|
||||
|
||||
This would let people express that the think the variable may be assigned null later on.
|
||||
|
||||
On the other hand, we could just permit these assignments when using `var`, and use flow analysis
|
||||
to ensure safety.
|
||||
|
||||
```C#
|
||||
var current = myLinkedList.Head;
|
||||
current = null; // no warning because var is nullable
|
||||
current.ToString(); // warning, the flow state says this may be null
|
||||
```
|
||||
|
||||
This would allow users to be explicit when they want to make sure not to assign
|
||||
null to a type, but they have to spell out the type.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Make `var` have a nullable annotated type and infer the flow type as normal.
|
|
@ -1,145 +1,69 @@
|
|||
# Upcoming meetings for 2019
|
||||
|
||||
## Schedule ASAP
|
||||
|
||||
## Schedule when convenient
|
||||
|
||||
- removing support for `#pragma warning enable` (https://github.com/dotnet/roslyn/issues/36550) (Julien)
|
||||
- disallow `expr!!` and use `parameter!!` (instead of `parameter!`) (Mads)
|
||||
- allow `#nullable` to mean `#nullable enable` (Julien)
|
||||
- effect of `[DoesNotReturn]` on various flow analyses? (https://github.com/dotnet/roslyn/issues/37081) (Julien)
|
||||
- suppressing LHS of compound assignment (https://github.com/dotnet/roslyn/issues/36617) (Julien)
|
||||
- warn on `[NonNull]` and other attributes outside of nullable context (https://github.com/dotnet/roslyn/issues/36588) (Julien)
|
||||
- confirm suppression on `ref` arguments (https://github.com/dotnet/roslyn/issues/35555) (Julien)
|
||||
- confirm features that are pushed out:
|
||||
- nullable attributes do not affect method bodies (`[AllowNull] T M() { return default; }`)
|
||||
- nullable attributes not checked in OHI
|
||||
- no diagnostics on misused nullable attributes
|
||||
- async LINQ (Julien)
|
||||
- Syntax of positional records/primary constructors (Andy)
|
||||
- Discussion of refreshing language spec (Neal)
|
||||
- Conceptual model for tuples (Mads, Neal)
|
||||
- Close on flag for warning waves (existing `/warn` or new `/warnVersion`?)
|
||||
|
||||
## Recurring topics
|
||||
|
||||
- *Triage championed features*
|
||||
- *Triage milestones*
|
||||
- *Design review*
|
||||
|
||||
## Sep 25, 2019
|
||||
|
||||
## Sep 18, 2019
|
||||
|
||||
## Sep 16, 2019
|
||||
|
||||
## Sep 11, 2019
|
||||
|
||||
- Close https://github.com/dotnet/roslyn/issues/37313 (Rikki, Phillip)
|
||||
- Triage new proposed [9.0 features](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+no%3Amilestone+label%3A%22Proposal+champion%22)
|
||||
- Finish triaging away [8.X milestone](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22+milestone%3A%228.X+candidate%22)
|
||||
|
||||
## Sep 4, 2019
|
||||
|
||||
- https://github.com/dotnet/roslyn/issues/37313 (Rikki, Phillip)
|
||||
|
||||
## Aug 28, 2019
|
||||
|
||||
- Triage language features
|
||||
|
||||
## Aug 26, 2019
|
||||
|
||||
- Triage language features
|
||||
|
||||
## May 22, 2019
|
||||
|
||||
- Confirm whether we want `abstract` modifier to be implied for interface implementations inside derived interfaces unless there is an implementation.
|
||||
|
||||
## May 1, 2019
|
||||
|
||||
* Nullable attributes - continue discussion
|
||||
* Nullable opt-in - do we need to adjust the story?
|
||||
|
||||
# C# Language Design Notes for 2019
|
||||
|
||||
Overview of meetings and agendas for 2019
|
||||
|
||||
## Dec 18, 2019
|
||||
|
||||
[C# Language Design Notes for Dec 18, 2019](LDM-2019-12-18.md)
|
||||
|
||||
1. Pure null checks
|
||||
2. `var?`
|
||||
|
||||
## Dec 16, 2019
|
||||
|
||||
[C# Language Design Notes for Dec 16, 2019](LDM-2019-12-16.md)
|
||||
|
||||
1. Switch Expression as a Statement Expression (continued) (Neal, Fred)
|
||||
2. Triage
|
||||
|
||||
## Dec 11, 2019
|
||||
|
||||
[C# Language Design Notes for Dec 11, 2019](LDM-2019-12-11.md)
|
||||
|
||||
1. Design review feedback
|
||||
|
||||
## Dec 9, 2019
|
||||
(not yet transcribed)
|
||||
- https://github.com/dotnet/csharplang/issues/2850 Proposed changes for pattern-matching (continued) (Neal)
|
||||
- https://github.com/dotnet/csharplang/issues/2860 Switch Expression as a Statement Expression (Neal)
|
||||
|
||||
## Dec 4, 2019 (Design Review)
|
||||
(not yet transcribed)
|
||||
|
||||
## Nov 25, 2019
|
||||
|
||||
[C# Language Design Notes for Nov 25, 2019](LDM-2019-11-25.md)
|
||||
|
||||
1. Revisit unconstrained `T?` (Mads, Jared)
|
||||
|
||||
## Nov 20, 2019
|
||||
(not yet transcribed)
|
||||
- https://github.com/dotnet/csharplang/issues/2911 Utf8 String Literals (Neal)
|
||||
- https://github.com/dotnet/csharplang/issues/2850 Proposed changes for pattern-matching (continued) (Neal)
|
||||
|
||||
## Nov 18, 2019
|
||||
|
||||
[C# Language Design Notes for Nov. 18, 2019](LDM-2019-11-18.md)
|
||||
|
||||
1. Proposed changes for pattern-matching
|
||||
|
||||
## Nov 13, 2019
|
||||
|
||||
[C# Language Design Notes for Nov 13, 2019](LDM-2019-11-13.md)
|
||||
|
||||
1. Discriminated unions
|
||||
2. Improve analysis of `[MaybeNull]T` values
|
||||
|
||||
## Nov 11, 2019
|
||||
|
||||
[C# Language Design Notes for Nov 11, 2019](LDM-2019-11-11.md)
|
||||
|
||||
1. Confirm removal of warning calling a method that returns `[MaybeNull]T`
|
||||
2. Allow interpolated string constant
|
||||
3. Enhancing the Common Type Specification
|
||||
4. Type pattern
|
||||
5. Simple name binding with target type
|
||||
|
||||
## Oct 30, 2019
|
||||
|
||||
[C# Language Design Notes for Oct 30, 2019](LDM-2019-10-30.md)
|
||||
|
||||
1. Function Pointer syntax
|
||||
2. Enhancing the Common Type Specification
|
||||
|
||||
## Oct 28, 2019
|
||||
|
||||
[C# Language Design Notes for Oct 28, 2019](LDM-2019-10-28.md)
|
||||
|
||||
1. Discard parameters in lambdas and other methods
|
||||
1. Enhancing the common type algorithm
|
||||
|
||||
## Oct 23, 2019
|
||||
|
||||
[C# Language Design Notes for Oct 23, 2019](LDM-2019-10-23.md)
|
||||
|
||||
1. New primitive types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/435
|
||||
|
||||
## Oct 21, 2019
|
||||
|
||||
[C# Language Design Notes for Oct 21, 2019](LDM-2019-10-21.md)
|
||||
|
||||
1. Records
|
||||
2. Init-only members
|
||||
3. Static lambdas
|
||||
|
||||
## Sep 18, 2019
|
||||
|
||||
[C# Language Design Notes for Sep 18, 2019](LDM-2019-09-18.md)
|
||||
|
||||
Triage:
|
||||
|
||||
1. Proposals with complete designs:
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/1888 Champion "Permit attributes on local functions"
|
||||
- https://github.com/dotnet/csharplang/issues/100 Champion "Target-typed new expression"
|
||||
|
||||
2. Target typing and best-common-type features:
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/2473 Proposal: Target typed null coalescing (??) expression
|
||||
- https://github.com/dotnet/csharplang/issues/2460 Champion: target-typed conditional expression
|
||||
- https://github.com/dotnet/csharplang/issues/881 Permit ternary operation with int? and double operands
|
||||
- https://github.com/dotnet/csharplang/issues/33 Champion "Nullable-enhanced common type"
|
||||
|
||||
## Sep 16, 2019
|
||||
|
||||
[C# Language Design Notes for Sep 16, 2019](LDM-2019-09-16.md)
|
||||
|
||||
- Support for Utf8 strings
|
||||
- Triage remaining features out of the [8.X milestone](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+milestone%3A%228.X+candidate%22)
|
||||
|
||||
## Sep 11, 2019
|
||||
|
||||
[C# Language Design Notes for Sep 11, 2019](LDM-2019-09-11.md)
|
||||
|
||||
1. Close https://github.com/dotnet/roslyn/issues/37313
|
||||
2. Triage new proposed [9.0 features](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+no%3Amilestone+label%3A%22Proposal+champion%22)
|
||||
2. Finish triaging away [8.X milestone](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22+milestone%3A%228.X+candidate%22)
|
||||
|
||||
## Sep 4, 2019
|
||||
|
||||
[C# Language Design Notes for Sep 4, 2019](LDM-2019-09-04.md)
|
||||
|
||||
1. AllowNull on properties https://github.com/dotnet/roslyn/issues/37313
|
||||
|
||||
## Aug 28, 2019
|
||||
|
||||
[C# Language Design Notes for Aug 28, 2019](LDM-2019-08-28.md)
|
||||
|
||||
1. Triage language features
|
||||
|
||||
## Jul 22, 2019
|
||||
|
||||
[C# Language Design Notes for July 22, 2019](LDM-2019-07-22.md)
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Jan. 6, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Use attribute info inside method bodies
|
||||
1. Making Task-like types covariant for nullability
|
||||
1. Casting to non-nullable reference type
|
||||
1. Triage
|
||||
|
||||
## Discussion
|
||||
|
||||
### Use attribute info inside method bodies
|
||||
|
||||
Examples:
|
||||
|
||||
1.
|
||||
|
||||
```C#
|
||||
bool TryGetValue<T>([MaybeNullWhen(false)]out T t)
|
||||
{
|
||||
return other.TryGetValue(out t); // currently warns
|
||||
}
|
||||
```
|
||||
|
||||
2.
|
||||
|
||||
```C#
|
||||
[return: MaybeNull]
|
||||
T GetFirstOrDefault<T>()
|
||||
{
|
||||
return null; // currently warns
|
||||
}
|
||||
```
|
||||
|
||||
3.
|
||||
|
||||
Overriding/implementation
|
||||
|
||||
```C#
|
||||
class A<T>
|
||||
{
|
||||
[return: MaybeNull]
|
||||
virtual T M();
|
||||
}
|
||||
|
||||
class B : A<string>
|
||||
{
|
||||
override string? M(); // warns about no [MaybeNull]
|
||||
}
|
||||
```
|
||||
|
||||
We don't have a complete design here, but some cases have an intuition about the correct
|
||||
behavior. In overriding, specifically, we need a specification for what it means for an
|
||||
annotation to be "compatible" with each of the attributes. On the other hand, it's not clear what
|
||||
the behavior of `MaybeNullWhenTrue` should be in all cases.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We'd like to do this if the return on investment seems worth it, but to fully evaluate
|
||||
we need a proposal of the work.
|
||||
|
||||
### Making Task-like objects nullable covariant
|
||||
|
||||
This is a pretty common pain-point, and it's not the first time we special-cased variance,
|
||||
specifically `IEquatable<T>` is treated as nullable contravariant. It's unfortunate that the CLR
|
||||
doesn't have capability to make `Task<T>` full covariant, but handling even nullable alone would
|
||||
be useful. Moreover, if we later get the capability to mark `Task<T>` covariant, this would not
|
||||
harm the effort.
|
||||
|
||||
We also think that there may be some special cases introduced for overload resolution where we
|
||||
consider `Task<T>` as covariant already. If we could reuse that knowledge, that would be useful.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's see if we can dig up the overload resolution changes for Task-like types and try to adapt
|
||||
the same rule for making Task-like types nullable covariant.
|
||||
|
||||
### Casting to non-nullable reference type
|
||||
|
||||
Example:
|
||||
|
||||
```C#
|
||||
BoundNode? node = ...;
|
||||
if (node.Kind == BoundKind.Expression)
|
||||
{
|
||||
var x = (BoundExpression)node; // warning if node is nullable
|
||||
}
|
||||
```
|
||||
|
||||
The question is if this warning is valuable, or annoying. We've hit this most often in Roslyn
|
||||
when using the pattern `(object)x == null` to do a null check while avoiding the user-defined
|
||||
equality check. This is annoying in the Roslyn codebase, but not very common outside it. On the
|
||||
other hand, there's feeling that when doing the BoundNode to BoundExpression check, which is less
|
||||
common in Roslyn but more common generally, there's agreement that the warning is useful in
|
||||
making the type annotated with the most accurate representation of the null state.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Keep the warning, no change. We think the warning is valuable for the non-null-check cases. Newer
|
||||
version of C# have features that address the null check problem and Roslyn should move to use `x
|
||||
is null` or similar.
|
||||
|
||||
## Triage
|
||||
|
||||
Three related proposals: #3037, #3038, #377.
|
||||
|
||||
These all deal with the general problem of statements in expressions, especially statements in
|
||||
switch expressions, and switch expression form in statements.
|
||||
|
||||
They don't necessarily require each other, but they fit a lot of the same syntax and semantic
|
||||
space, so we should consider them all together.
|
||||
|
||||
There's also a sketch for how we could unify the syntax forms of all of three proposals, with
|
||||
potential syntax changes.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We agree that all of these proposals are addressing important scenarios, and some improvement
|
||||
here is valuable. We're not sure where we want to go with generalized statements-in-expressions
|
||||
vs. adding special syntax forms for switch expression/statement.
|
||||
|
||||
We're mainly concerned that if we do switch expression blocks, we want to make sure that the we
|
||||
don't block a future generalization to all expressions. We need to find a generalization that we
|
||||
like, reject a generalization and accept this syntax, or put these improvements on the
|
||||
back-burner if we think that a generalization is possible, we just haven't found it.
|
||||
|
||||
Accepted for C# 9.0 investigation.
|
|
@ -1,188 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Jan. 8, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Unconstrained type parameter annotation
|
||||
2. Covariant returns
|
||||
|
||||
## Discussion
|
||||
|
||||
### Unconstrained type parameter annotation T??
|
||||
|
||||
You can only use `T?` when is constrained to be a reference type or a value type. On the other
|
||||
hand, you can only use `T??` when `T` is unconstrained.
|
||||
|
||||
The question is what to use for the following:
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract void F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
class B2 : A<int>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
class B3 : A<int?>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // Is ?? allowed or required?
|
||||
}
|
||||
```
|
||||
|
||||
Our understanding is that this would be:
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract void F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// We think the correct annotation is
|
||||
// void F<U>(ref U? u) where U : class
|
||||
// because the type parameter is no longer unconstrained. The `where U : class`
|
||||
// constraint is required, as U? would otherwise mean U : struct
|
||||
}
|
||||
// We may want to allow U?? even in the above case, so
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default; // allowed?
|
||||
// This would not require the `where U : class` constraint because `U??` cannot
|
||||
// be confused with `Nullable<U>`
|
||||
}
|
||||
class B2 : A<int>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// The correct annotation is
|
||||
// void F<U>(ref U u)
|
||||
// We could allow
|
||||
// void F<U>(ref U?? u)
|
||||
// although it would be redundant
|
||||
}
|
||||
class B3 : A<int?>
|
||||
{
|
||||
internal override void F<U>(ref U?? u) => default;
|
||||
// The correct annotation is
|
||||
// void F<U>(ref U u)
|
||||
// We could allow
|
||||
// void F<U>(ref U?? u)
|
||||
// although it would be redundant
|
||||
```
|
||||
|
||||
In the above, we're wondering whether we should allow `??` without warning even in cases where
|
||||
there's existing syntax to represent the semantics. One benefit is that you could write
|
||||
|
||||
```C#
|
||||
abstract class A<T>
|
||||
{
|
||||
internal abstract F<U>(ref U?? u) where U : T;
|
||||
}
|
||||
class B1 : A<string>
|
||||
{
|
||||
internal override F<U>(ref U?? u) { }
|
||||
}
|
||||
```
|
||||
|
||||
Since the override cannot be confused with `U?`, there's no need for the `where U : class`. On the
|
||||
other hand, the benefit seems marginal and it's not needed to represent the semantics. It could
|
||||
also be added later.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like the syntax `??` to represent the "maybe default" semantics. We think that `??` should be
|
||||
allowed in the cases where we have other syntax to represent the semantics. A warning will be
|
||||
provided and hopefully a code fix to move the code to the "more canonical" form. The syntax `??`
|
||||
is only legal on type parameters in a nullable-enabled context.
|
||||
|
||||
We considered using the `?` syntax the represent the same semantics, but ruled it out for a couple reasons:
|
||||
|
||||
1. It's technically difficult to achieve. There are two technical limitations in the compiler.
|
||||
The first is design history where a type parameter ending in `?` is assumed to be a struct. This
|
||||
has been true in the compiler all the way until nullable reference types in the last release. The
|
||||
second problem is that many pieces of C# binding are very sensitive to being asked if a type is a
|
||||
value or reference type and asking the question can often lead to cycles if asked before the answer
|
||||
is absolutely necessary. However, `T?` means different things if `T` is a struct or unconstrained, so
|
||||
finding the safe place to ask is difficult.
|
||||
|
||||
2. Unresolved design problems. In overriding `T?` means `where T : struct`, going back to the beginning of
|
||||
`Nullable<T>`. This already caused problems with `T? where T : class` in C# 8, which is why we introduced
|
||||
a feature where you could specify `T? where T : class` in an override, contrary to past C# design where
|
||||
constraints are not allowed to be re-specified in overrides. To accommodate overloads for unconstrained
|
||||
`T?` we would have to introduce a new type of explicit constraint meaning *unconstrained*. We don't have
|
||||
a syntax for that, and don't particularly want one.
|
||||
|
||||
3. Confusion with a different feature. If we use the `T?` syntax, the following would be legal:
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public T? M<T>() where T : notnull { ... }
|
||||
}
|
||||
```
|
||||
|
||||
what you may think is that `M` returns a nullable reference type if `T` is a reference type, and a nullable
|
||||
value type if `T` is a value type (i.e., `Nullable<T>`). However, that's *not* what this feature is. This
|
||||
feature is essentially *maybe default*, meaning that `T?` may contain the value `default(T)`. For a reference
|
||||
type this would be `null`, but for a value type this would be the zero value, not `null`.
|
||||
|
||||
Moreover, this seems like a useful feature, that would be ruled out if we used the syntax for something else.
|
||||
Consider a method similar to `FirstOrDefault`, `FirstOrNull`:
|
||||
|
||||
```C#
|
||||
public static T? FirstOrNull<T>(this IEnumerable<T> e) where T : notnull
|
||||
```
|
||||
|
||||
The benefit is that there is a single sentinel value, `null`, and that the full set of values in a struct
|
||||
could be represented. In `FirstOrDefault`, if the input is `IEnumerable<int>` there is no way to distinguish
|
||||
a non-empty enumerable with first value of `0`, or an empty enumerable. With `FirstOrNull` you can distinguish
|
||||
these cases in a single call, as long as `null` is not a valid value in the type.
|
||||
|
||||
Due to CLR restrictions it is not possible to implement this feature in the obvious way, so this feature may
|
||||
never be implemented, but we would like to prevent confusion and keep the syntax available in case we ever
|
||||
figure out how we'd like to implement it.
|
||||
|
||||
### Covariant returns
|
||||
|
||||
We looked at the proposal in #2844.
|
||||
|
||||
There's a proposal that for the following
|
||||
|
||||
```C#
|
||||
interface I1
|
||||
{
|
||||
object M();
|
||||
}
|
||||
interface I2 : I1
|
||||
{
|
||||
string M() => null;
|
||||
}
|
||||
```
|
||||
|
||||
`I2.M` would be illegal as it is, because this is an interface *implementation*, not an
|
||||
*override*. Interface implementation would not change return types, while overriding would. Thus
|
||||
we would allow
|
||||
|
||||
```C#
|
||||
interface I1
|
||||
{
|
||||
object M();
|
||||
}
|
||||
interface I2 : I1
|
||||
{
|
||||
override string M() => null;
|
||||
}
|
||||
```
|
||||
|
||||
which would allow the return type to change. However, it would override *all* `M`s, since
|
||||
explicit implementation is not allowed.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like it in principle and would like to move forward. There may be some details around
|
||||
interface implementation vs. overriding to work out.
|
|
@ -1,159 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Jan. 15th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Working with data
|
||||
2. Record feature breakdown
|
||||
|
||||
## Discussion
|
||||
|
||||
### Working with data
|
||||
|
||||
As we discuss records, we want to go over a design document we produced a number of years ago
|
||||
called "working with data." This document lays out how, when we design features, we inherently
|
||||
express a "path of least resistance," which consists of the features that seem easiest or
|
||||
shortest to use to accomplish a given problem.
|
||||
|
||||
Link: https://github.com/dotnet/csharplang/issues/3107
|
||||
|
||||
The document argues that, as we find particular patterns to be more effective at building
|
||||
software, we should make the forms we find to be more effective simpler or shorter to express in
|
||||
the language. We should not "change" our opinions, meaning make old syntax illegal, but we should
|
||||
"level the playing field" by making other forms simpler.
|
||||
|
||||
The conclusion of the design document is that we should favor
|
||||
|
||||
1. Immutable members in records by default
|
||||
1. Any features from records that we separate should not make the simple syntax longer
|
||||
|
||||
### Record feature breakdown
|
||||
|
||||
We've also been working on breaking down the individual features
|
||||
of records and determining how independent they can or should be.
|
||||
|
||||
Notes: https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
There seem to be the following somewhat separable parts of records
|
||||
|
||||
1. Value-based equality
|
||||
2. Construction boilerplate
|
||||
3. Object initializers
|
||||
4. Nondestructive mutation
|
||||
5. Data-friendly defaults
|
||||
|
||||
#### Value equality
|
||||
|
||||
It's been proposed that a `key` modifier could be applied to signal that value-based equality is
|
||||
being generated based on the members which have it. This works in many cases,
|
||||
but if the absence of the `key` modifier means inherited equality, we're not sure
|
||||
that's the semantics we want. It would also not allow value-based equality to be
|
||||
"specified" in the base class in some sense, enforcing value equality for the deriving
|
||||
type. Whether this is valuable or blocking is an open question.
|
||||
|
||||
#### Construction boilerplate
|
||||
|
||||
Creating a constructor to assign members of a container is one of the largest sources
|
||||
of repetitive boilerplate, e.g.
|
||||
|
||||
```C#
|
||||
public class Point
|
||||
{
|
||||
public int X { get; }
|
||||
public int Y { get; }
|
||||
public Point(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can imagine various points on this spectrum to simplify the boilerplate,
|
||||
|
||||
```C#
|
||||
public class Point
|
||||
{
|
||||
public key int X { get; }
|
||||
public key int Y { get; }
|
||||
public Point(X, Y); // name matching and type absence implies initialization
|
||||
}
|
||||
```
|
||||
|
||||
Which removes the duplication of naming the same elements multiple times or,
|
||||
|
||||
```C#
|
||||
public class Point(X, Y)
|
||||
{
|
||||
public key int X { get; }
|
||||
public key int Y { get; }
|
||||
}
|
||||
```
|
||||
|
||||
Which removes the constructor name duplication and we could go further to remove
|
||||
property name duplication,
|
||||
|
||||
```C#
|
||||
public class Point(
|
||||
public key int X { get; }
|
||||
public key int Y { get; }
|
||||
);
|
||||
```
|
||||
|
||||
Going all the way to the original position deconstruction
|
||||
|
||||
```C#
|
||||
public class Point(int X, int Y);
|
||||
```
|
||||
|
||||
Where we pick a point in this space seems to correspond to the perceived benefits
|
||||
of the orthogonality of the feature. If the construction shorthand is useful for
|
||||
many scenarios outside of the record scenarios, it's practical to expand it.
|
||||
|
||||
### Object initializers
|
||||
|
||||
One benefit to object initializers is that they don't refer to a constructor directly,
|
||||
only to the properties. This sidesteps a weakness in C#, where constructor initialization in inheritance requires repetition. Without constructors the simple
|
||||
relation
|
||||
|
||||
```C#
|
||||
public abstract class Person
|
||||
{
|
||||
public string Name { get; }
|
||||
}
|
||||
public class Student : Person
|
||||
{
|
||||
public string ID { get;}
|
||||
}
|
||||
```
|
||||
|
||||
has no repetition. Each class states only the properties that are essential, and
|
||||
for derived classes all the base properties are inherited without repetition.
|
||||
Once you add constructors this breaks down
|
||||
|
||||
```C#
|
||||
public abstract class Person
|
||||
{
|
||||
public string Name { get; }
|
||||
public Person(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
public class Student : Person
|
||||
{
|
||||
public string ID { get; }
|
||||
public Student(string id, string name)
|
||||
: base(name)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now the derived classes have to repeat everything from the base, causing brittleness
|
||||
along the boundary. If we were to imagine some improvement to object initializers,
|
||||
then defining a constructor would not be required.
|
||||
|
||||
On the other hand, this also removes one of the main benefits for having a constructor,
|
||||
namely that you can validate the whole state of the object before producing it.
|
|
@ -1,108 +0,0 @@
|
|||
|
||||
# C# Language Design Notes for Jan. 22, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Top-level statements and functions
|
||||
2. Expression Blocks
|
||||
|
||||
## Discussion
|
||||
|
||||
### Top-level statements and functions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3117
|
||||
|
||||
Three main scenarios:
|
||||
|
||||
1. Simple programs are simple -- remove the boilerplate for Main
|
||||
|
||||
2. Top-level functions. Members outside of a class.
|
||||
|
||||
3. Scripting/interactive. Submission system allows state preservation across evaluations.
|
||||
|
||||
Unfortunately, some of these proposals interact in difficult ways.
|
||||
|
||||
If you write
|
||||
|
||||
```C#
|
||||
int x = ...;
|
||||
```
|
||||
|
||||
is `x` now a global mutable variable for the entire program? Or is it a
|
||||
local variable in a generated Main method?
|
||||
|
||||
#### Proposal: Simple programs
|
||||
|
||||
The proposal is to prioritize (1) and (3) and remove boilerplate, while
|
||||
enabling use in scripting/interactive scenarios.
|
||||
|
||||
To address (1), we would allow a single file in the compilation to contain top-level statements,
|
||||
and any file to contain top-level local functions, which would be in scope in all files, but it
|
||||
would be an error to refer to them.
|
||||
|
||||
There's wide consensus that (1) is very useful. There's the case of small programs, where you
|
||||
really just want to write a few statements and not have to write the boilerplate of classes and
|
||||
Main. It's also a very large learning burden in that just to write "Hello, World" requires
|
||||
explaining methods, classes, static, etc.
|
||||
|
||||
(3) is also important partly because there are a number of products and scenarios
|
||||
currently using the scripting system. We should keep that in mind to make sure that
|
||||
we don't prevent a large number of use cases from ever using the new system.
|
||||
|
||||
We think (2) is interesting and worth considering. It may not be the highest priority,
|
||||
but we need to make sure we don't rule it out entirely. We also think that if we add
|
||||
(1) it seems likely that some people would want (2) much sooner.
|
||||
|
||||
If we do want to make space for (2) we should make sure to look at lookup rules very carefully.
|
||||
The C# lookup rules are very complicated and including new ones for top-level members could
|
||||
include subtle ways that change new code.
|
||||
|
||||
When we designed scripting we had experience that copying back-and-forth from interactive and the
|
||||
main program is very useful and important. Because the syntax used here is similar to local
|
||||
functions and it's not currently proposed that accessibility modifiers are legal, this would
|
||||
create a difference when copying code between standard C# and the interactive dialect, since
|
||||
presumably those declarations would now be illegal.
|
||||
|
||||
#### Block expressions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
We're revisiting the earlier discussions and there is a proposal for how we could
|
||||
make blocks legal as expressions. The proposed precedence would be the lowest
|
||||
possible, so many ambiguities or breaking changes would be avoided.
|
||||
|
||||
Examples:
|
||||
|
||||
```C#
|
||||
var x = { ; 3 }; // int x = 3
|
||||
var x = { {} 3 }; // int x = 3
|
||||
```
|
||||
|
||||
Note that a final expression is required, so the `'` or `{}` are necessary as "starting
|
||||
statements".
|
||||
|
||||
The most notable restrictions are that you cannot branch out, meaning that `return`, `yield break`, and `yield return`, and `goto` would be illegal in this proposal.
|
||||
|
||||
Something which was brought up before is whether to use a "trailing expression" to
|
||||
produce a value, or introduce some sort of statement to produce the evaluation
|
||||
expression. If we used a `break e;` syntax, the above could look like
|
||||
|
||||
```C#
|
||||
var x = { break 3; };
|
||||
```
|
||||
|
||||
One problem is turning the block into a lambda, where `break` would have to be changed to
|
||||
`return`. On the other hand, if this code were introduced directly into the method, `return`
|
||||
would actually produce different, valid semantics. `break e;` would be an error in both contexts,
|
||||
instead of producing different code.
|
||||
|
||||
The precedence doesn't have agreement. Some people think that the precedence is
|
||||
still too high and that we should almost always require a parenthesized wrapper
|
||||
expression, except in specific cases where we think it's clear. Other people think
|
||||
that this is too low and they want to use them in more places.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We don't think we have enough information about the restrictions we're working under. One way to
|
||||
make progress would be to construct a list of the potential ambiguities in using the `{}` as an
|
||||
expression term.
|
|
@ -1,106 +0,0 @@
|
|||
|
||||
# C# Language Design for Jan. 29, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Record -- With'ers
|
||||
|
||||
## Discussion
|
||||
|
||||
In this meeting we'd like to talk about Mads's write-up of the "With-ers" feature, as it relates
|
||||
to records. Multiple variations have been proposed, but the suggestion generally takes the form
|
||||
of a `with` expression that can return a copy of a data type, with selective elements changed.
|
||||
|
||||
Write-up: https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
The first thing we learned is that the fundamental problem we're trying to solve is
|
||||
"non-destructive mutation."
|
||||
|
||||
There are two approaches we've thought of: direct copy and then direct modification, and creation of a new type based on the values of the old type.
|
||||
|
||||
1. Direct copy. We might call this "copy-and-update" because we copy the new data type exactly,
|
||||
then update the new type with required changes. The basic implementation would be to use
|
||||
MemberwiseClone, and then overwrite select properties.
|
||||
|
||||
2. Create a new type. we call this "constructing through virtual factories." If the type supports
|
||||
a constructor, this approach would call the constructor using the new values, or the existing
|
||||
ones if nothing new is given. The construction would be virtual so that derived types would not
|
||||
lose state when called through the base type.
|
||||
|
||||
There are advantages and disadvantages to each proposal.
|
||||
|
||||
(1) is simple but seemingly dangerous. There are often internal constraints to a type which must
|
||||
be preserved for correctness. Usually this is enforced through the type constructor and
|
||||
visibility of modification. That would not necessarily be available here.
|
||||
|
||||
(2) does construction similar to conventional construction today, so it doesn't introduce as many
|
||||
safety concerns. On the other hand, the contract looks a lot more complicated. To make the
|
||||
feature seem simple on the surface, it looks like we imply a lot of implicit dependency. For
|
||||
example,
|
||||
|
||||
```C#
|
||||
public data class Point(int X, int Y);
|
||||
var p2 = p1 with { Y = 2 };
|
||||
```
|
||||
|
||||
Would generate
|
||||
|
||||
```C#
|
||||
public class Point(int X, int Y)
|
||||
{
|
||||
public virtual Point With(int X, int Y) => new Point(X, Y);
|
||||
}
|
||||
var p2 = p1.With(p1.X, 2);
|
||||
```
|
||||
|
||||
The first requirement is that an auto-generated `With` method must have a primary constructor, in
|
||||
order to know which constructor to call. Alternatively, we could have a `With` method generated
|
||||
for every constructor, although that would require a syntax to signal that `With` methods should
|
||||
be generated in the absence of a primary constructor.
|
||||
|
||||
The compiler also needs to know that the `X` and `Y` parameters of the `With` method correspond
|
||||
to particular properties, so it can fill in the defaults in the `with` expression. Otherwise we
|
||||
would need some way of signifying which of the parameters are meant to be "defaults":
|
||||
|
||||
```C#
|
||||
public class Point(int X, int Y)
|
||||
{
|
||||
public virtual Point With(bool[] provided, int X, int Y)
|
||||
{
|
||||
return new Point(provided[0] ? X : this.X, provided[1] ? Y : this.Y);
|
||||
}
|
||||
}
|
||||
var p2 = p1.With(new bool { false, true}, default, 2);
|
||||
```
|
||||
|
||||
We also need to figure out which `With` method to call at a particular call site. One way is to
|
||||
construct an equivalent call and perform overload resolution. Another way would be to pick a
|
||||
particular `With` method as primary, and always use that one in overload resolution.
|
||||
|
||||
This also has some of the same compatibility challenges that we've seen in other areas.
|
||||
Particularly, if you add members to the record, there will be a new `With` method with a new
|
||||
signature. This would break existing binaries referencing the old `With` method. In addition, if
|
||||
you add a new `With` method, the old one would still be chosen by overload resolution, if
|
||||
overload resolution is performed, as long as unspecified properties in the `with` expression are
|
||||
default values.
|
||||
|
||||
On the other hand, this is also a general problem with backwards compatibility overloads. We'll
|
||||
need to investigate whether we want to add a general purpose mechanism for handling backwards
|
||||
compatibility and if we want to introduce a special case for With-ers specifically.
|
||||
|
||||
What all of the above interdependency implies is that we need a significant amount of syntax or
|
||||
"hints" about what to do during autogeneration. We previously expressed interest in providing
|
||||
orthogonality for as many of the "record" features as possible. A conclusion is that
|
||||
auto-generated With-ers require or suggest many of syntactic and semantic components of records
|
||||
themselves. When we try to separate the feature entirely, we require user opt-in to specify the
|
||||
"backing" state of the With-er. This seems to imply that auto-generation should
|
||||
not be a general, orthogonal feature, but a specific property of records.
|
||||
|
||||
However, we don't have to give up orthogonality entirely. The requirements for auto-generated
|
||||
With-ers doesn't imply anything about manually written With-ers. Auto-generation seems possible
|
||||
in records because the syntax ties the state to the public interface. Manual specification looks
|
||||
just like the components of records that can be written explicitly in regular classes, like
|
||||
constructors themselves. If we do want to pursue this avenue, we should try to limit
|
||||
the complexity of the pattern as much as possible. It's not too bad if it's fully
|
||||
generated by the compiler, but it can't be very complicated if we want users to write
|
||||
it themselves.
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
# C# LDM for Feb. 3, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Value equality
|
||||
|
||||
## Discussion
|
||||
|
||||
We split our discussion between two proposals, which end up being very
|
||||
similar.
|
||||
|
||||
### 'key' equality proposal
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/3127
|
||||
|
||||
Q: Is comparing `System.Type`s slow? Does it require reflection?
|
||||
|
||||
A: `typeof` does not require reflection and comparing `Type`s is fast.
|
||||
|
||||
Q: Why use a KeyEquals method?
|
||||
|
||||
A: To signify that value equals is used and delegate to the base equality,
|
||||
when the base opts-in to value equality. By having a well-known signature
|
||||
in metadata, no special attributes are required for derived types to discover
|
||||
the pattern.
|
||||
|
||||
Q: Is KeyEquals necessary? Can we use `EqualityContractOrigin` to figure
|
||||
out that the type implements value equality?
|
||||
|
||||
A: Yes, that seems like it should work.
|
||||
|
||||
*Discussion*
|
||||
|
||||
There's some concern that modifying the public surface area of the type
|
||||
itself without any type of modifier is too much magic. If we have some
|
||||
sort of modifier that goes on the type, in addition to the "key" members,
|
||||
it would be clear that the type implements value equality from the type
|
||||
declaration, in addition to the member declarations.
|
||||
|
||||
This dovetails into records as a whole in that it would allow the feature sets to be separable.
|
||||
If a type could have value equality or be a record, the features could be combined to produce a
|
||||
value record, or the value equality could be left off to allow a record with reference equality.
|
||||
There's some disagreement on whether this is a positive or a negative. If you view a record as
|
||||
appropriately having value equality, this is a negative, or vice versa.
|
||||
|
||||
### 'value' equality proposal
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
The most visible difference here is that `value` is the name of the modifier, instead of `key`.
|
||||
This more accurately reflects the term "value equality", but it's unfortunate that we already
|
||||
have the term "value type" which has a completely different meaning in the language.
|
||||
|
||||
At the moment the proposal also doesn't include the "extra" members, like a strongly
|
||||
typed Equals, the `==`/`!=` operators, and `IEquatable` interface implementation.
|
||||
|
||||
There's an open question as to whether this feature is preferred for a discriminated
|
||||
union scenario or not. We have two examples in Roslyn of discriminated unions, our
|
||||
symbol tree and our bound tree, and they have almost completely different equality
|
||||
contracts.
|
|
@ -1,133 +0,0 @@
|
|||
|
||||
# C# LDM for Feb. 5, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Dependent nullability attribute
|
||||
2. Null checking in unconstrained generics
|
||||
|
||||
## Discussion
|
||||
|
||||
### Nullability
|
||||
|
||||
Dependent calls:
|
||||
|
||||
We'd like to be able to support patterns like:
|
||||
|
||||
```C#
|
||||
if (x.HasValue)
|
||||
{
|
||||
x.Value // should not warn when HasValue is true
|
||||
}
|
||||
```
|
||||
|
||||
We would add attributes to support these use cases: `EnsuresNotNull` and `EnsuresNotNullWhen` (to parallel the existing `NotNull` attributes). The
|
||||
proposal as stands is to name the fields or properties and that would be
|
||||
the inputs and outputs for the flow analysis. We propose that the lookup
|
||||
rules for resolving the names in these instances would be similar to the
|
||||
rules for the `DefaultMemberAttribute`.
|
||||
|
||||
This could also be used for helpers in constructor initialization, where today constructors which
|
||||
only call `base` are required to initialize all members, even if a helper initializes some of the
|
||||
members.
|
||||
|
||||
There's a follow-up question: should you be able to specify that members of the parameter are
|
||||
not-null after the call? For example,
|
||||
|
||||
```C#
|
||||
class Point
|
||||
{
|
||||
public object? X;
|
||||
}
|
||||
static void M([EnsuresNotNull(nameof(Point.X))]Point p) { ... }
|
||||
```
|
||||
|
||||
We could also allow it for nested annotation
|
||||
|
||||
```C#
|
||||
class Point
|
||||
{
|
||||
public object? X;
|
||||
}
|
||||
class Line
|
||||
{
|
||||
public Point P1;
|
||||
public Point P2;
|
||||
}
|
||||
static void M([EnsuresNotNull("P1.X", "P1.Y")]Line l) { ... }
|
||||
static bool M([EnsuresNotNullWhen(true, "P1.X", "P1.Y")]Line l) { ... }
|
||||
```
|
||||
|
||||
The nested names could also be used for return annotations.
|
||||
|
||||
However, we're not sure this is worth it. We see the usefulness in theory,
|
||||
but we're not sure how often it would actually be used. If we want to leave
|
||||
the space for later, we could produce an error when writing an attribute with
|
||||
an unsupported string form.
|
||||
|
||||
Similarly, if we don't want to support referring to members of types through
|
||||
the parameters, as in the first example, we can also provide an error for these
|
||||
scenarios. Or, we could say that the initial proposal is qualitatively different
|
||||
from all these scenarios:
|
||||
|
||||
```C#
|
||||
[MemberNotNull(nameof(X))]
|
||||
void M() { }
|
||||
```
|
||||
|
||||
For the situations in parameters and return types we are referring to the target the attribute is
|
||||
being applied to, while the original proposal is returning to the containing type, somewhat
|
||||
unrelated to the location of the attribute.
|
||||
|
||||
We're also not sure exactly what the name or shape of these attributes would
|
||||
look like. We think this could be valuable, but we'd like to decide with
|
||||
the full context of other attributes we're considering.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We see the usefulness of the original scenario, but the broadening we're not sure
|
||||
on the return on investment. Let's support the original scenario through a new
|
||||
attribute, `MemberNotNull`, that only has members as valid attribute targets to start.
|
||||
|
||||
If we find users still hit significant limitations without the parameter and return
|
||||
type support, we can consider broadening in a future release.
|
||||
|
||||
### Pure non-null check in generic
|
||||
|
||||
```C#
|
||||
public T Id<T>(T t)
|
||||
{
|
||||
if (t is null) Console.WriteLine();
|
||||
return t; // warn?
|
||||
}
|
||||
```
|
||||
|
||||
The question here is whether we should consider `T` to move to `MaybeDefault` after
|
||||
checking for `null`. We never do this today.
|
||||
|
||||
The scenario where a "pure" null check would come into play is:
|
||||
|
||||
```C#
|
||||
public T Id<T>(T t) where T : notnull
|
||||
{
|
||||
if (t is null) Console.WriteLine();
|
||||
return t;
|
||||
}
|
||||
```
|
||||
|
||||
It appears that this does not warn today, which looks like a bug. The analogous
|
||||
scenario for `T??` is
|
||||
|
||||
```C#
|
||||
public T ID<T>(T t)
|
||||
{
|
||||
if (t is default) Console.WriteLine();
|
||||
return t;
|
||||
}
|
||||
```
|
||||
|
||||
However, this is illegal as there is no way to check if `T` is `default(T)`.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The original scenario should not warn.
|
|
@ -1,48 +0,0 @@
|
|||
|
||||
# C# Language Design for Feb. 10, 2020
|
||||
|
||||
# Agenda
|
||||
|
||||
Records
|
||||
|
||||
# Discussion
|
||||
|
||||
We're continuing our attempt to draw out the dependencies and individual features inside records.
|
||||
|
||||
When going through the list, what stands out is:
|
||||
|
||||
- Looking at `with`, we need to figure out what's mentionable in the `with` expression.
|
||||
|
||||
- We need to figure out exactly what we want for how primary constructors fit into records
|
||||
|
||||
There are a number of positives and negatives of primary constructors. On the negative side,
|
||||
a non-record primary constructor seems to consume syntactic space that could be used for
|
||||
records. If we think that records are the overwhelmingly common scenario, then it seems like
|
||||
using the shortest syntax for the most common feature is useful. On the positive side, primary
|
||||
constructors alone seem to support a simpler way of writing private implementation details.
|
||||
Separate from the value as a whole, there's some desire to have a special keyword just for
|
||||
records. That is, even if we didn't do primary constructors, it could be valuable to have
|
||||
an explicit modifier, like `data` to signify that this type has special behavior.
|
||||
|
||||
One possible pivot is to eliminate some composition syntax entirely, by creating a new type
|
||||
of declaration, `record`, e.g.
|
||||
|
||||
```C#
|
||||
record Point(int X, int Y);
|
||||
```
|
||||
|
||||
This would be equivalent to the syntax that we've been discussing with `data`, namely
|
||||
|
||||
```C#
|
||||
data class Point(int X, int Y);
|
||||
```
|
||||
|
||||
but since the `class` keyword is implied by default, the most common scenario would be
|
||||
just about as short as the shorter `class Point(int X, int Y)` form.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
After taking everything into account, we think having an new keyword for records is good both for
|
||||
leaving space for non-record primary constructors, and also to serve as a clear signifier of
|
||||
record semantics.
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
|
||||
# C# Language Design for Feb 12, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records
|
||||
|
||||
## Discussion
|
||||
|
||||
### Value equality
|
||||
|
||||
Proposal: use the `key` keyword previously mentioned, but also
|
||||
require it on the type declaration as well, e.g.
|
||||
|
||||
```C#
|
||||
key class HasValueEquality
|
||||
{
|
||||
public key int X { get; }
|
||||
}
|
||||
```
|
||||
|
||||
There are a number of things we could pivot on
|
||||
|
||||
```C#
|
||||
key class HasValueEquality1 { public key int X { get; } }
|
||||
class HasValueEquality2 { public key int X { get; } }
|
||||
key class HasValueEquality3 { public key X { get; } }
|
||||
class HasValueEquality4 : IEquatable<HasValueEquality4> { public int X { get; } }
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
```C#
|
||||
record Point1(int X); // Implies value equality over X
|
||||
record Point2a(int X); // Implies inherited equality
|
||||
key record Point2b1(int X); // Implies value equality over X
|
||||
key record Point2b2a(int X); // Implies "empty" value equality
|
||||
key record Point2b2b(key int X); // Implies value equality over X
|
||||
|
||||
|
||||
key class Point3a(int X); // implies record + value equality over X
|
||||
data class Point3b(int X); // implies record with inherited equality
|
||||
```
|
||||
|
||||
#### Equality default
|
||||
|
||||
We originally considered adding value equality on records both because it's difficult to
|
||||
implement yourself and it fits the semantics we built for records in general. We want to validate
|
||||
that these things are still true, and new considerations, namely whether it is the appropriate
|
||||
default for records and whether it should be available to other types, like regular classes.
|
||||
|
||||
We left off in the previous discussion asking whether value equality is not just
|
||||
an inconvenient default, but actively harmful for key scenarios for records. Some examples
|
||||
we came up with are either classes with large numbers of members, where value equality may
|
||||
be unnecessary and slow, and circular graphs, where using value equality could cause
|
||||
infinite recursion.
|
||||
|
||||
These do seem bad, but it's not obvious that these scenarios either fit perfectly with the
|
||||
canonical record, or if the consequences are necessarily worse than default reference equality.
|
||||
Certainly producing infinite recursion in object graphs is bad, but silently incorrect behavior
|
||||
due to inaccurate reference equality is also harmful, in the same sense. It's also easier
|
||||
to switch from value equality to reference equality than it is to switch from reference equality
|
||||
to value equality, due to the complex requirements in a value equality contract.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Value equality seems a reasonable default, as long as they are immutable by default, and that
|
||||
there is a reasonable way to opt-in to a different equality.
|
||||
|
||||
#### Separable value equality
|
||||
|
||||
Given that we like value equality as a default, we have to decide if we want a separable equality
|
||||
feature as well. This is important for the scenario:
|
||||
|
||||
```C#
|
||||
record Point1(int X)
|
||||
{
|
||||
public int X { get; }
|
||||
}
|
||||
```
|
||||
|
||||
if there's a separate `key` feature, we need to decide if the substituted property should
|
||||
require, allow, or disallow the `key` modifier, e.g.
|
||||
|
||||
```C#
|
||||
record Point1(int X)
|
||||
{
|
||||
public key int X { get; }
|
||||
}
|
||||
```
|
||||
|
||||
We also need to decide what such a "separable" equality feature would look like, and if it has a
|
||||
difference between records and other classes. We could add a `key` feature for non-records, and
|
||||
disallow `key` entirely in records. The members of a record equality would then not be
|
||||
customizable.
|
||||
|
||||
The individual `key` modifiers on non-records seem deceptively complicated.
|
||||
|
||||
A common case is "opt-in everything". `key` modifiers wouldn't improve much on this, as they
|
||||
would be necessary on every element. On the other hand, there are often computed properties that
|
||||
may be seen as part of "everything", but not part of the equality inputs. The plus of record
|
||||
primary constructors is that they identify the "core" inputs to the type.
|
||||
|
||||
Individual `key` modifiers also do not help with the large custom classes that are written today
|
||||
where it's easy to forget to add new members to equality. With a `key` modifier you can still
|
||||
forget to add the modifier to a new member.
|
||||
|
||||
These decisions play into records as a whole because they affect the uniformity of record and
|
||||
non-record behavior. If records are defined by their "parameters", namely in this syntax the
|
||||
primary constructor parameters and identically named properties, then no other members should
|
||||
be a part of the equality. However, that would imply members in the body are not automatically
|
||||
included. For regular classes, it seems backwards. Members are not generally included, they have
|
||||
to be added specifically.
|
||||
|
||||
On the other hand, if we prioritize uniformity, general members in record bodies would be included
|
||||
in equality, which would harm a view of records as consisting primarily of the "record inputs."
|
|
@ -1,82 +0,0 @@
|
|||
|
||||
# C# Language Design for Feb. 19, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
State-based Value Equality
|
||||
|
||||
## Discussion
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/3213
|
||||
|
||||
* We haven't decided (yet) to add support for value equality on all
|
||||
classes (separate from records)
|
||||
|
||||
* The behavior is actually that all fields _declared_ in the class are
|
||||
members in the value equality, not all fields in the class (since inherited fields are not
|
||||
included)
|
||||
|
||||
* Inheritance would be implemented using the previously described
|
||||
proposals using the `EqualityContract` property
|
||||
|
||||
* Records wouldn't behave differently, except that they have `value` by
|
||||
default
|
||||
|
||||
* The main difference with how records work in other places is that the semantics
|
||||
of a record is otherwise decided by the members of the primary constructor, while in this
|
||||
proposal the members of the record primary constructor have no special contribution to the value
|
||||
equality semantics
|
||||
|
||||
* There's an evolution risk where we want to provide more complex things, like deep
|
||||
equality, but these features don't support enough complexity to add it. Instead, we end up just
|
||||
adding more keywords or more attributes. Consider array fields. The default equality is reference
|
||||
equality, but sequence equality isn't particularly rare. How would users customize that?
|
||||
A new keyword? Attribute? Writing Equals manually?
|
||||
|
||||
* Turns out we're finding a lot of customization pivots. String comparison is another one.
|
||||
If we want to support all these scenarios attributes could be better. If we could use
|
||||
attributes to supply an EqualityComparer that would be almost completely customizable.
|
||||
|
||||
* If equality is this complicated, should we only support simple generated equality for
|
||||
records? Can we leave more complicated scenarios to tooling, like source generators?
|
||||
|
||||
Record equality: use the "primary" members or use all fields?
|
||||
|
||||
* Using all the fields is consistent with how structs work
|
||||
|
||||
* Using the "primary" members mirrors how the generation of `With` or other things
|
||||
generated by a record with a primary constructor
|
||||
|
||||
* There does seem to be a possibility that after you get to a certain size, positional
|
||||
records are less useful. In that case we want a path to the nominal record. If we do want the
|
||||
nominal path, it's generally desirable that we want as little "record" syntax as possible.
|
||||
If we choose the struct "use all the fields" approach, then we could use exactly the
|
||||
same mechanism for both the "nominal" and the "positional" records.
|
||||
|
||||
* The nominal record syntax that has been floated is
|
||||
|
||||
```C#
|
||||
record Point { int X; int Y; }
|
||||
```
|
||||
|
||||
which generates
|
||||
|
||||
```C#
|
||||
record Point
|
||||
{
|
||||
public int X { get; init; }
|
||||
public int Y { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
Aside from the shorthand for properties, this generates Equals, GetHashCode and some form
|
||||
of "With", which doesn't seem much different from proposals for a separable value equality. Is
|
||||
there really much point in separating these proposals?
|
||||
|
||||
* One completely opposite possibility: bypass the question by prohibiting private members in the
|
||||
positional record entirely
|
||||
|
||||
**Conclusion**
|
||||
|
||||
No hard decisions yet. Leaning slightly towards using "all declared fields" as the metric for
|
||||
value equality. There's some support for the "no private fields approach."
|
|
@ -1,41 +0,0 @@
|
|||
|
||||
# C# Language Design for Feb. 24, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Nominal records proposal
|
||||
|
||||
## Discussion
|
||||
|
||||
### Nominal records
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3226
|
||||
|
||||
We've been trying to leave space open for something we're calling "nominal records" where the
|
||||
concept is that we establish some new system for constructing types based on names, instead of
|
||||
the order of parameters in a constructor.
|
||||
|
||||
Here we have a refreshed nominal records proposal to examine and consider.
|
||||
|
||||
The proposal says:
|
||||
|
||||
> The main thing you lose out on with nominal construction is a centralized place - the
|
||||
constructor body - for validation. Property setters can have member-wise validation, but
|
||||
cross-member holistic validation is not possible. However, for a feature such as records that is
|
||||
for data not behaviors, that seems to be a particularly small sacrifice.
|
||||
|
||||
We don't necessarily agree that this is a small restriction, and there may be some way to add
|
||||
support for it.
|
||||
|
||||
When it comes to the `With` we do need to decide what members are copied over in non-destructive
|
||||
mutation. One strategy is to use the "surface area" of the object, which is defined as the
|
||||
constructor parameters, along with the public fields and properties that have some sort of
|
||||
"setter".
|
||||
|
||||
Alternatively, we could copy over the state of the object. This would be equivalent to the use
|
||||
of the `MemberwiseClone` approach as we discussed in previous design meetings.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
There are many details to work out, but there's consensus that we want to investigate adding
|
||||
nominal records in the future.
|
|
@ -1,123 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for Feb. 26, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Design Review
|
||||
|
||||
## Discussion
|
||||
|
||||
Today is a design review, where we collect the design team, selected emeritus
|
||||
members, and a number of broad ecosystem experts to provide some "in-the-moment"
|
||||
feedback to our current designs and their directions.
|
||||
|
||||
Today we presented more of our thoughts on the top-level statements/"simple programs"
|
||||
features and records.
|
||||
|
||||
### Simple Programs
|
||||
|
||||
We have a prototype of simple programs. As per the existing spec, you can have
|
||||
top-level functions among all files, and other statements in a single file.
|
||||
|
||||
Collected feedback, not in any particular order:
|
||||
|
||||
* Supporting local functions in files other than the top-level statement file doesn't
|
||||
seem useful and could cause confusion. If there's a local function defined in a file
|
||||
only with classes, it would appear that that function would be in scope for the
|
||||
classes, according to C# lexical scoping conventions. However, since these are defined
|
||||
as *local functions*, not top-level methods, it would be an error to use them inside
|
||||
the class. Moreover, even if that confusion is resolved, there doesn't seem to be a
|
||||
compelling reason to allow it in the first place. Because these functions are not
|
||||
accessible from anything except the main statement file, it would be most likely to
|
||||
want to put the functions in that file, next to the uses. The only benefit from allowing
|
||||
local functions in separate file may be as a helper file that is linked into other
|
||||
compilations. However, wrapping these utility functions in a class so they can be used
|
||||
in more places seems like a small burden with big benefits. Once wrapped in a class,
|
||||
these functions are simply methods like in C# today.
|
||||
|
||||
* Mixing classes and statements in the same file could generate some confusion. The existing
|
||||
design is that classes can see the variables created by statements, but it would be illegal
|
||||
to reference them. This keeps the option open to allow access later. However, this could be
|
||||
a confusing middle ground. To simplify the situation we could require only statements in the
|
||||
top-level in one file (forcing all types to be declared in separate files). However, there is
|
||||
interest in using utility classes in the top-level statements, perhaps especially with a
|
||||
forthcoming records feature that provides simple, short syntax for declaring new types.
|
||||
|
||||
* Many of these features mirror what we already have in CSX. It's good that our
|
||||
current designs are similar and allow these constructs in more places, but since the semantics of
|
||||
this design have subtle differences from CSX this would effectively create a third dialect of C#.
|
||||
There's some desire to unify these worlds, but it's difficult. CSX is designed to allow all
|
||||
values to be persisted, which is important for the scripting "submission" system, but this makes
|
||||
a number of types of statements illegal that we have support for in the current design, like
|
||||
ref locals. It also creates a burden for new designs, where statements need to be explicitly
|
||||
designed for both CSX and C#. For example, the new `using var` declaration form is nonsensical
|
||||
under the CSX design and probably should be illegal. Since the current 'simple programs' design
|
||||
effectively treats statements like they are part of a method body, there's a cleaner semantic
|
||||
parallel with C#, meaning less special-casing.
|
||||
|
||||
### Records
|
||||
|
||||
Here we presented a variety of different pieces of designs we have been thinking about.
|
||||
|
||||
#### Nominal Record
|
||||
|
||||
Feedback:
|
||||
|
||||
* One of the biggest drawbacks of the current writeable-property style in C#, where types are
|
||||
declared with public mutable properties that are then initialized using object initializers,
|
||||
is that author can't enforce that certain properties are always initialized. It would be a
|
||||
big disappointment if any "nominal records" feature that we built couldn't support this feature.
|
||||
|
||||
* With the design as-is there's no way to validate the whole state of the object. However, that's
|
||||
also true of the object-initializer style currently in use, and this doesn't seem to be as a big
|
||||
of a problem for current users.
|
||||
|
||||
#### Value Equality
|
||||
|
||||
* Positive feelings on generating `.Equals(T)` and implementing `IEquatable<T>`, mixed feelings
|
||||
on generating the `==` and `!=` operators.
|
||||
|
||||
* If we opt-in the whole class using `value class`, we also need an opt-out for individual members.
|
||||
Regular classes also often have somewhat specialized equality requirements, like wanting to compare
|
||||
certain lists as sequence-equal, or compare strings ignoring case. This observation points to a
|
||||
lot more customization points for value equality on general classes than value equality on records.
|
||||
|
||||
* Using value equality on mutable state is seems dangerous if the type is used in a dictionary,
|
||||
but despite the danger, other languages (Java, Go) have value equality for common types, like
|
||||
lists, that can be easily added to a dictionary.
|
||||
|
||||
* We don't currently have a robust mental model for what it means to be a "value class." Is "value
|
||||
class" a separable concept from "implementing value equality," which people often do today? Or
|
||||
is it not a different type of class, but simply a modifier signaling an implementation detail,
|
||||
namely that the compiler generates value equality automatically. If we think of value equality
|
||||
as a public contract, how does that change our view of existing code? Classes can currently
|
||||
override Equals, but we don't distinguish what *kind* of Equals they provide. That isn't a
|
||||
language concept, in a sense, but a part of the documentation.
|
||||
|
||||
### Nominal Records
|
||||
|
||||
* When using the `with` expression on nominal records, the generated parameter-less `With` method
|
||||
looks a lot like `Clone`. It does little aside from return a new object with a shallow copy of
|
||||
the state. If `With` is essentially Clone, why not use one of the existing forms of Clone that we
|
||||
already have?
|
||||
|
||||
* ICloneable is deprecated and MemberwiseClone is protected. Maybe we should just call the method
|
||||
Clone(), but not override or implement any of the other framework uses.
|
||||
|
||||
* This feature looks a lot like structural typing from other languages, like Javascript's "spread"
|
||||
operator, but that is not the feature we're currently trying to build. This is feature is still
|
||||
about declaring new types, not providing some compatibility between existing types.
|
||||
|
||||
* We spent a lot of time talking about validation and "validators", a very recent concept that was
|
||||
floated as an alternative to constructors, executing after the `with` expression.
|
||||
|
||||
* There's some general concern about having no capability of validation, but no consensus on
|
||||
exactly how validators should work.
|
||||
|
||||
* If validators work against the copied fields of the object, that seems to imply that the
|
||||
fields are the state being operated on. On the other hand, only certain members can be
|
||||
mentioned in the `with` expression. Why wouldn't those be the things that are copied? Instead
|
||||
of all the state?
|
||||
|
||||
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
|
||||
# C# Language Design for March 9, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Design review
|
||||
2. Records
|
||||
|
||||
## Discussion
|
||||
|
||||
### Simple Programs
|
||||
|
||||
In the design review we covered the "simple programs" feature. A big piece of feedback is that
|
||||
they didn't like the "middle ground" we had carved out with local functions. Right now we have
|
||||
local functions that can exist both in the top-level statement "file" and also across other
|
||||
files. Because the design allows only local functions at the top level, those functions are only
|
||||
accessible from and can only access the statements in the "top-level statement file."
|
||||
|
||||
The feedback was that this would be an unfortunate design point. Organizing local functions in
|
||||
other files, when they can't be accessed by other files, is a big problem. There are two places
|
||||
we could go with this. We could either pull back and allow many of these forms only in the
|
||||
"top-level statement file." Or we could go the other way and allow more functionality at the top
|
||||
level, like allowing truly top-level members, including functions and variables, that can be
|
||||
declared and referenced from everything in the program.
|
||||
|
||||
Allowing full top-level members is attractive but opens a lot of questions. The most important is
|
||||
top-level variables. By allowing top-level variables and giving them the C# default of
|
||||
mutability, we would effectively be enabling mutable global variables. There is collective
|
||||
agreement that in everything but the smallest programs this is a bad programming practice and we
|
||||
shouldn't encourage it.
|
||||
|
||||
We could try to pivot on syntactic differences. One major difference between local variables and
|
||||
functions and member-level variables and functions is that members allow accessibility modifiers.
|
||||
Top-level statements could be, by default, local variables. Accessibility modifiers, `public`,
|
||||
`private`, `internal`, et al., could differentiate between top-level and local variables.
|
||||
However, this feels like it may be too subtle a design point, relying too much on minor syntactic
|
||||
decisions to decide things like scoping.
|
||||
|
||||
The biggest takeaway is that this is a complex topic with a lot possibilities. Maybe we could try
|
||||
to carve out a small portion to make some progress, without committing to a narrow design for the
|
||||
entire space of "top-level members." We generally like this approach, but CSX makes things
|
||||
difficult. Since the existing design focuses on local variables and local functions,
|
||||
compatibility with any sort of submission system is a problem. Fundamentally, we would need to
|
||||
decide which pieces of state in a C# program are "transient," in that they cannot be referenced
|
||||
from a new submission, and which are "preserved."
|
||||
|
||||
The value still seems important enough to move forward. There's general agreement that top-level
|
||||
statements are useful. Some people think they are useful in the simple form already presented,
|
||||
while other people want to see this as the starting point for a full feature, and these views are
|
||||
roughly compatible.
|
||||
|
||||
If we move forward with our subset, we need to flesh out the mental model for how it works.
|
||||
Specifically, it's important to note why top-level variables and functions would be inaccessible
|
||||
from inside classes. One way to think about this is the difference between instance and static.
|
||||
When you're in a static context, all the instance variables are visible, but not accessible. You
|
||||
could also model it as the statements are directly inserted into Main (which is true).
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We'd like to move forward with the prototype with the modification that top-level statements can
|
||||
only appear in one file. Symbols declared in these statements would be visible, but an error to
|
||||
access inside classes. All the top-level statements are treated as if they were inside an async
|
||||
Main method.
|
||||
|
||||
### Value equality
|
||||
|
||||
When we discussed records in the design meeting we brought up value equality for records and a
|
||||
proposal for extending to regular classes. A big shift was that records should have an easy
|
||||
global automatic value equality, while general classes should never have a global opt-in.
|
||||
|
||||
This seems contradictory, but if records have a set of default semantics that naturally fit value
|
||||
equality, then having it enabled by default is suitable. Value equality plays particularly well
|
||||
with immutability. Since records strongly support immutable programming, supporting value
|
||||
equality is natural. For arbitrary classes, however, it's not clear at all how value equality
|
||||
should behave. Opt-ing in either all or only some members seems to have downsides for many class
|
||||
examples.
|
||||
|
||||
We're not ruling out value equality for regular classes, but for the future we'd like to examine
|
||||
specifically how we'd like value equality to work for records. This could impact how and when we
|
||||
bring generated value equality to conventional user classes.
|
|
@ -1,103 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for March 23rd, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Triage
|
||||
2. Builder-based records
|
||||
|
||||
## Discussion
|
||||
|
||||
### Triage
|
||||
|
||||
#### Generic constraint `where T : ref struct`
|
||||
|
||||
Proposal: #1148
|
||||
|
||||
This is a very complicated area. It's probably not good enough to add this generic constraint,
|
||||
because things like default interface methods create a boxed `this` parameter. It's likely that
|
||||
we would need runtime support to make this safe.
|
||||
|
||||
This is somwewhat related to the designs in the shapes/roles proposal in that it's about using
|
||||
the "shape" of an interface, possibly with more restrictions than interfaces themselves. Since
|
||||
both proposals may require runtime changes it would be valuable to batch up those changes
|
||||
together.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Push to at least C# 10.0. Should be considered in concert with the shapes/roles proposals.
|
||||
|
||||
#### Improve overload resolution for delegate compatibility
|
||||
|
||||
Proposal: #3277
|
||||
|
||||
This is a parallel to changes we previously made to betterness where we remove overloads
|
||||
that will later be considered invalid, to be removed from the overload resolution candidate
|
||||
list in the beginning. This functionally will cause more overload resolution calls to succeed,
|
||||
since invalid candidates will be removed and this will prevent overload resolution ambiguitiy.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Tentatively added to C# 9.0. We'd like this proposal for function pointers, so if we were to
|
||||
implement this for function pointers and correct overload resolution for delegates at the same
|
||||
time, that would be desirable.
|
||||
|
||||
#### Allow GetEnumerator from extension methods
|
||||
|
||||
Proposal: #600
|
||||
|
||||
First thing is to confirm that there's no compat concern. When we tried to extend the behavior
|
||||
for `IDisposable` we ran into a problem because `foreach` *conditionally* disposes things which
|
||||
match the `IDisposable` pattern. This means that if anything starts satisfying the pattern which
|
||||
didn't before, an existing method may be newly called. If we ever conditionally use the `foreach`
|
||||
pattern this would probably also be a breaking change.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Willing to accept it any time, as long as we confirm that it's not a breaking change.
|
||||
|
||||
### Builder-based records
|
||||
|
||||
When discussing records we've had various
|
||||
designs that focus on "nominal" scenarios, where the members of the record are set by name, instead of lowering into method parameters. One proposed implementation strategy is a new series of rules around initialization that we've sometimes called "initonly." We've also looked at using struct "builders" in the past for a similar purpose, and would like to revisit some of these discussions.
|
||||
|
||||
We have a proposal from a community member, @HaloFour, that lays out another example implementation strategy that we're using for discussion.
|
||||
|
||||
https://gist.github.com/HaloFour/bccd57c5e4f3261862e04404ce45909e
|
||||
|
||||
There are certainly some advantages to this structure:
|
||||
|
||||
* Uses existing valid C# to implement the pattern, making it compatible with existing compilers. If
|
||||
we avoid usage of features like `ref struct` and `in`, it could be compatible for even older
|
||||
consumers, since this would be binary compatible with C# 2.0 metadata.
|
||||
|
||||
* Allows the type being built to always see the whole set of property values being initialized,
|
||||
meaning that the author of the type can validate the new state of the object.
|
||||
|
||||
* No new runtime support for any features (e.g., does not require covariant returns)
|
||||
|
||||
There are also some disadvantages.
|
||||
|
||||
Performance could be a problem. For classes, the biggest concern is stack size. Currently,
|
||||
initializing a class with an object initializer only requires a single pointer on the class and
|
||||
then each member is initialized separately, the initializer values don't all need to be on the
|
||||
stack simultaneously. If we use a struct builder, the entire builder needs to be on the stack
|
||||
before initialization.
|
||||
|
||||
For structs, there is the extra stack space cost, but having a builder also effectively doubles
|
||||
the metadata size of the every struct. It's also potentially harder for the CLR to optimize the
|
||||
initialization and remove dead stores through the double-struct passing. If we go forward with
|
||||
this approach we should consult the JIT for their perspective.
|
||||
|
||||
We're also not sure that this approach fully eliminates all brittleness in adding/changing fields
|
||||
and properties across inheritance, especially if the inheritance is split across assemblies and
|
||||
only one author is recompiled, or they are recompiled in a different order. If we can find a way
|
||||
to close those holes, or limit the feature to prevent these situations, that could be an
|
||||
important mitigating factor.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
There's a difficult balance here. Some members are focused on about performance, some prioritize
|
||||
ecosystem compat, and others prioritize "cleanliness" of design, in different directions. Almost
|
||||
everyone has a different priority and prefers different approaches for different reasons. We need
|
||||
to discuss things more and reduce some of the unknowns.
|
|
@ -1,117 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for March 25, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Questions around the new `nint` type
|
||||
|
||||
2. Target-typed new
|
||||
|
||||
## Discussion
|
||||
|
||||
Issue #3259
|
||||
|
||||
### LangVersion
|
||||
|
||||
THe question is what the behavior of the compiler should be when seeing an `nint`
|
||||
type in `langversion` C# 8. Our convention is that the compiler never preserves
|
||||
old *behavior* for older language versions. For instance, we do not preserve the
|
||||
code for older code generation strategies and switch to that with the language
|
||||
version flag. Instead, `langversion` is meant to be guard rails, providing
|
||||
diagnostics when features are used that aren't available in older versions of the
|
||||
language.
|
||||
|
||||
There are a few options we could take.
|
||||
|
||||
1. Make an exception for `nint`, allowing them to be seen and compiled like an
|
||||
`IntPtr` in `langversion` C# 8.
|
||||
|
||||
2. Make a wider divergence between `nint` and `IntPtr`. Adding a `modreq` to
|
||||
the emitted `IntPtr` type would make them effectively unusable by older language
|
||||
versions and other languages.
|
||||
|
||||
3. Preserve the behavior, as long as no new semantics are used. For instance,
|
||||
using the arithmetic operators on `nint` and on `IntPtr` have different semantics.
|
||||
It would be an error to use any of these operators in older language versions.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We think (3) is the best balance.
|
||||
|
||||
### `IntPtr` and `nint` operators
|
||||
|
||||
We have two proposals:
|
||||
|
||||
1. Remove built-in identity conversions between native integers and underlying types and add explicit conversions.
|
||||
|
||||
2. Remove `nint` operators when using the `IntPtr` type
|
||||
|
||||
**Conclusion**
|
||||
|
||||
(1) is a little too harsh. Let's do (2).
|
||||
|
||||
### Behavior of constant folding
|
||||
|
||||
The concern is platform dependence.
|
||||
|
||||
In the following example
|
||||
|
||||
```C#
|
||||
const nint m = int.MaxValue;
|
||||
const nint u1 = unchecked(m + 1);
|
||||
nint u2 = unchecked(m + 1);
|
||||
```
|
||||
|
||||
if the machine is 32-bit, then the result overflows. If the machine is 64-bit, it does not.
|
||||
|
||||
While it's possible in the existing language to produce constant-folded values which are
|
||||
undefined, we don't think that behavior is desirable for nint.
|
||||
|
||||
The main contention is what to do in a `checked` context if we know the value will overflow
|
||||
32-bits. We could either produce an error, saying that this will overflow on some platforms,
|
||||
or produce a warning and push the calculation to runtime, warning that the calculation may
|
||||
overflow at runtime (and produce an exception).
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Whenever we can safely produce a constant value under 32-bits, we do constant folding. Otherwise,
|
||||
the result is non-constant, and under `checked`, the code produces a warning and the result
|
||||
is non-constant.
|
||||
|
||||
### Interfaces on `nint`?
|
||||
|
||||
Should interfaces on `IntPtr` and `nint` match? Or should `nint` only accept a certain set of
|
||||
compiler-validated interfaces on `IntPtr`?
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We trust that interfaces will only be added to `IntPtr` with recognition that those interfaces
|
||||
also affect `nint`. We'll make all interfaces on `IntPtr` available on `nint`, with `IntPtr`
|
||||
occurrences substituted for `nint`.
|
||||
|
||||
## Target-typed `new`
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md
|
||||
|
||||
Clarification about library evolution: if a user uses `new()`, adding a constructor to a type
|
||||
can produce an ambiguity. Similarly, if a method is called with `new()` that can produce an
|
||||
ambiguity if more overloads of that method is added. This is analogous with `null` or `default`,
|
||||
which can convert to many different types and can produce ambiguity.
|
||||
|
||||
The spec currently specifies that there are a list of types where target-typed new is allowed. To
|
||||
simplify, we propose that we specify that target-typed new should produce a fully-typed `new` and
|
||||
the legality of that expression is defined elsewhere. This does make `new()` work on enums, which
|
||||
is currently proposed as illegal because it may be confusing. However, `new Enum()` is legal
|
||||
today, so we think that it should be allowed for target-typed `new` simply because of
|
||||
consistency.
|
||||
|
||||
There's some debate on what it should do for nullable value types. On the one hand, the rule
|
||||
"new() is just shorthand for writing out the type on the left," implies that the result should be
|
||||
`null`. On the other hand, the nullable lifting rules would imply that the base type of the
|
||||
target should be the underlying type, not the nullable type. Overall, we think that `new`ing the
|
||||
underlying type makes the most sense, both because it's the most useful (we already have a
|
||||
shorthand for `null`) and because it's likely what the user intended.
|
||||
|
||||
For `dynamic`, we will not permit it simply because `new dynamic()` is also illegal.
|
||||
|
||||
Final thought: many thanks to @alrz for the great contribution!
|
|
@ -1,112 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for March 30, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records
|
||||
|
||||
1. Builders vs init-only
|
||||
|
||||
2. Possible conflicts with discriminated unions
|
||||
|
||||
3. Value equality
|
||||
|
||||
## Discussion
|
||||
|
||||
### Builders vs. init-only
|
||||
|
||||
We discussed more of the tradeoffs of using builders vs using an "init-only" feature for records,
|
||||
and looked into requirements for other languages, including VB and F#. Based on our current
|
||||
designs, the work needed to consume the new features for "init-only" seem fairly small.
|
||||
Recognizing the `modreq` and allowing access to init-only members, and calling any necessary
|
||||
"validator" on construction, are pretty simple features for VB. VB already has the syntactic
|
||||
requirements for the feature (object initializers), and we'd like to keep it possible for VB to
|
||||
consume major changes in the API surface, if not write those new features. F# is undergoing
|
||||
active development and changes are certainly viable there. Because the scope of changes is more
|
||||
open-ended in F#, it's possible it could feature more implementation work.
|
||||
|
||||
Notably, most of the features associated with "init-only" and "validators" do not require a new
|
||||
runtime, only a new compiler version. The new compiler version is necessary to recognize safety
|
||||
rules (validators must always be called after constructors, if they exist), but they don't use
|
||||
any new features in the CLR. The only feature potentially requiring runtime features is
|
||||
overriding a record with another record, which could potentially require covariant returns.
|
||||
|
||||
The remaining differences seem to come down to whether you can "see" the whole object during
|
||||
initial construction (as opposed to validation). If you can see the whole object immediately,
|
||||
that makes writing a `With` method that avoids a copy if all the values are identical very
|
||||
straightforward. However, this could be done for "init-only" as well, by moving this semantic to
|
||||
the `with` expression, optionally comparing the arguments to the `With` expression with the
|
||||
values on the receiver object and avoiding calling With if they are identical. Therefore we don't
|
||||
think we're actually ruling anything out by going down the "init-only" route.
|
||||
|
||||
There are advantages in going down the "init-only" route instead. The performance for structs
|
||||
is probably better and more optimizable, and the IL pattern seems clearer and less bloated.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like the "init-only" IL better. Given the path forward for other languages and compatibility
|
||||
with many runtimes, we think it's a better future as an IL pattern.
|
||||
|
||||
### Conflicts with discriminated unions
|
||||
|
||||
There was a general question if these decisions could impact a future discriminated unions feature.
|
||||
We don't have a lot in mind, but if we do end up building a discriminated union made of the records
|
||||
feature, there is one component we may want to reserve. Discriminated unions often have a set of simple
|
||||
data members. For instance, if we wrote a Color enum as a discriminated union, it could look like.
|
||||
|
||||
```C#
|
||||
enum class Color
|
||||
{
|
||||
Red,
|
||||
Green,
|
||||
Blue
|
||||
}
|
||||
```
|
||||
|
||||
If we reduce these to records, it may look like:
|
||||
|
||||
```C#
|
||||
abstract class Color
|
||||
{
|
||||
public class Red() : Color;
|
||||
public class Green() : Color;
|
||||
public class Blue() : Color;
|
||||
}
|
||||
```
|
||||
|
||||
The problem is: since those classes are effectively singletons, we'd like for the instances to be
|
||||
cached by the compiler, to avoid allocations. However, we don't currently have a syntax in C# that
|
||||
means "singleton." Scala uses the "empty record" syntax to mean singleton. We need to decide if we'd
|
||||
like to reserve that syntax ourselves, or find some other solution.
|
||||
|
||||
### Value equality
|
||||
|
||||
We've decided that we want value equality by default for records. We need to settle on what that
|
||||
means. The primary proposal on the table is shallow-field-equality, namely comparing all fields
|
||||
in the type via EqualityComparer. This would match the semantic we have decided on for "Withers,"
|
||||
where the shallow state of the object is copied, similar to MemberwiseClone.
|
||||
|
||||
There's a large segment of the LDM that thinks doing anything except for field comparison is
|
||||
problematic because it introduces far too many customization points for the record feature.
|
||||
Almost all custom equality would have to deal with sequence equality and string equality, which
|
||||
are already very different mechanisms. There's a proposal that we could provide further
|
||||
customization via source generators, which could allow almost any customization.
|
||||
|
||||
A follow-up to that is: why have value equality by default at all? Why not use source generators
|
||||
for all value equality? One problem with that is that we want the simplest records to be very
|
||||
short. The other problem is that we effectively have to pick an equality (C# will inherit one if
|
||||
you don't). We previously decided that value equality is a non-controversial default -- if it's
|
||||
wrong it's probably not worse than when reference equality is wrong, and it's often better.
|
||||
Struct-like field-based equality is a simple and familiar version of value equality.
|
||||
|
||||
We also need to decide on value-equality as a separable feature for regular classes. Some people
|
||||
like the idea of a separate feature and would be fine even with the constrained version that only
|
||||
compares fields. Others don't think this feature meets the hurdles for a new language feature. If
|
||||
we don't have customization options, it may be rarely used, and it seems possible that a source
|
||||
generator version could be the much more popular version. It's also worth noting that, as a
|
||||
separable feature, we don't need to add separable equality now.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
No separable value equality for non-records, right now. Default record equality is defined as
|
||||
field-based shallow equality.
|
|
@ -1,119 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for April 1, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Function pointer calling conventions
|
||||
|
||||
2. `field` keyword in properties
|
||||
|
||||
## Discussion
|
||||
|
||||
### Function Pointers
|
||||
|
||||
There are a few open issues and proposals for generalization of function pointers based on
|
||||
new runtime features.
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3324
|
||||
|
||||
#### NativeCallableAttribute
|
||||
|
||||
The attribute already exists in the CLR, and allows for specifying a calling convention other than
|
||||
`managed` (which means that it can't be called from C#, but could be used from function pointers).
|
||||
|
||||
The question is what level of support we want to provide in the language for this attribute.
|
||||
|
||||
Since the runtime behavior is to crash if the method is called incorrectly (meaning, invoked at
|
||||
all from C# if not through a function pointer), we almost certainly want to recognize the
|
||||
attribute's existence and provide errors for incorrect usage.
|
||||
|
||||
We considered more restrictions than the ones mentioned in the issue (only usable in function
|
||||
pointers, parameters must be blittable, must be static) like restricted accessibility or special
|
||||
syntax. The consensus is that is too much work for a small feature.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
`NativeCallableAttribute` should be recognized and the restrictions are accepted, with the
|
||||
addition that generics are also prohibited in the method and all containing types, recursively,
|
||||
and delegate conversion is also prohibited.
|
||||
|
||||
#### Supporting extra calling conventions
|
||||
|
||||
The existing proposal mandates that the only the existing calling conventions are supported. We
|
||||
previously said we'd consider new calling conventions when they were proposed by the runtime. We now
|
||||
have proposals about some likely new calling convention from the runtime.
|
||||
|
||||
The proposal is that the "calling convention" syntax in function pointers could be a general identifier, and legal values for the runtime would be determined by name matching against type names
|
||||
starting with `CallConv` in a particular namespace, and passing through Unicode lower-case mapping.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Accepted.
|
||||
|
||||
#### Attributes on function pointer types
|
||||
|
||||
The syntax is getting a bit verbose, but allowing an extra axis for customization seems like the
|
||||
simplest extension of function pointers that provides the level support that the runtime may need
|
||||
in the future.
|
||||
|
||||
Currently our favored syntax is:
|
||||
|
||||
```C#
|
||||
delegate* cdecl[SuppressGCTransition, MyFuncAttr]<void> ptr;
|
||||
```
|
||||
|
||||
The attribute-like syntax would be turned into the `modreq`s on the function pointer type that
|
||||
would be used by the runtime to encode special calling behavior. These would also effectively be
|
||||
different types at the C# level and would not have implicit conversions between them.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Accepted, assuming there are no problems in implementation.
|
||||
|
||||
### `field` keyword in properties
|
||||
|
||||
Over the years there have been many requests for similar features, e.g.
|
||||
https://github.com/dotnet/csharplang/issues/140
|
||||
|
||||
Maybe the simplest version is that there is a contextual identifier, e.g. `field`, which refers
|
||||
to an implicit backing field of the property. This seems useful, but a big limitation is that the
|
||||
backing field of the property must have the same type as the property. If lazy initialization is
|
||||
a common case, that seems likely to require differing property types, as the backing field would
|
||||
often be nullable, but the initialized field would be not nullable.
|
||||
|
||||
On the other hand, biting off too much in a single feature may delay simple scenarios
|
||||
unnecessarily. Should we try to address the simplest scenarios first, and leave more complex
|
||||
scenarios for later? In this case we probably need to find what scenarios are served by that
|
||||
design. Two things that are recognized are simple validation, e.g.
|
||||
|
||||
```C#
|
||||
public int PositiveValue
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
throw new ArgumentException("Cannot be negative")
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and registration like `INotifyPropertyChanged`
|
||||
|
||||
```C#
|
||||
public int P
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
PropertyChanged();
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Let's confirm that these scenarios are the ones most commonly requested and that they aren't
|
||||
addressed or modified by any of the other scheduled language features, e.g. source generators for
|
||||
INotifyPropertyChanged. From there we can discuss the specific proposal with a better
|
||||
understanding of the problem and solution space.
|
|
@ -1,86 +0,0 @@
|
|||
|
||||
# C# Language Design Notes for April 6th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Init-only members
|
||||
|
||||
## Discussion
|
||||
|
||||
We have a proposal to dive into: https://github.com/jaredpar/csharplang/blob/init/proposals/init.md
|
||||
|
||||
* The proposal notes that you can set `init` fields of the base type during construction, similar
|
||||
to `readonly`. This is not how `readonly` works today, only the declaring type can set readonly
|
||||
fields
|
||||
|
||||
* The proposal allows `init` on class and struct declarations as a shorthand for `init` on types.
|
||||
This is different from how `readonly` struct works today, where there is no syntactic shorthand,
|
||||
`readonly` simply adds the additional requirement that all instance fields are marked `readonly`.
|
||||
|
||||
* For the runtime: does this feature prohibit runtime restrictions on setting `readonly` instance
|
||||
fields in the future? Put simply: yes. To avoid breaking C#, the runtime would be required to
|
||||
either respect the proposed `InitOnlyAttribute`, or restrict optimizations to not alter the code
|
||||
for these features.
|
||||
|
||||
* Use in interfaces: the proposal prohibits it, but the following example seems useful:
|
||||
|
||||
```C#
|
||||
interface I
|
||||
{
|
||||
int Prop { get; init set; }
|
||||
}
|
||||
|
||||
public void MyMethod<T>() where T : I, new()
|
||||
{
|
||||
var t = new T() {
|
||||
Prop = 1
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
* Signature compatibility: should `init` properties be compatible with mutable ones? That is, should
|
||||
removing `init` in favor of a bare `set` be binary-compatible? This impacts our decisions for how
|
||||
we think about safety in older compilers:
|
||||
|
||||
* If we use a modreq to prevent other compilers from unsafely using a `setter`, that affects the
|
||||
signature of the method, and would make the above a breaking change
|
||||
|
||||
* If we want accept that older compilers are not a problem (C#, VB, and F# will all be updated),
|
||||
perhaps we don't need to specially guard this at all
|
||||
|
||||
* We could use attributes to mark *and* guard, by using the `Obsolete` attribute. `ref struct`s
|
||||
use this guard by having an Obsolete attribute with a reserved message, that is ignored by
|
||||
compatible compilers.
|
||||
|
||||
### Accessors
|
||||
|
||||
Should we allow three accessors: `get, set, init`? A big problem here is that you can end up
|
||||
calling instance members in a constructor that invoke the setter, not the initter, for a
|
||||
property. This means that there are few, if any, invariants that hold for init vs. set, and
|
||||
weakens the feature significantly.
|
||||
|
||||
### Syntax
|
||||
|
||||
`init` vs. `initonly` for syntax. On the one hand, `init` is inconsistent with `readonly` (vs. `initonly`), but on the other hand we're pretty sad that `readonly` is such a long keyword. It also has few analogies
|
||||
with properties, where `readonly` isn't allowed at all, and the shorter `init` keyword seems more similar to
|
||||
`get` and `set`
|
||||
|
||||
* Usage of `init` as a modifier: there are a number of different meanings here, and similarities
|
||||
between `readonly` and `init` is separating the more places we use it. For instance, if `init` is
|
||||
allowed as a member modifier, it seems similar to `readonly` on members, but they actually do
|
||||
different things. Moreover, `readonly` on types means that all instance fields and methods are
|
||||
`readonly`, while `init` on types would only mean `init` on fields, not on methods.
|
||||
|
||||
* What are the uses for `init` methods, aside from helper methods? Could they be used in collection initializers?
|
||||
|
||||
### Required Initialization
|
||||
|
||||
The proposal in this doc is that required initialization is also an important feature for setters. We're
|
||||
going to leave that discussion for a future meeting. As proposed, nullable warnings are not modified for
|
||||
`init` members.
|
||||
|
||||
**Conclusions**
|
||||
|
||||
No `init` on types. No agreement on syntax. We probably have to talk about more of the use cases.
|
||||
We've decided that having three different accessors is not helpful. Settled that we will place a
|
||||
`modreq` on all `init` setters.
|
|
@ -1,154 +0,0 @@
|
|||
|
||||
# C# Language Design for April 8, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `e is dynamic` pure null check
|
||||
2. Target typing `?:`
|
||||
3. Inferred type of an `or` pattern
|
||||
4. Module initializers
|
||||
|
||||
## Discussion
|
||||
|
||||
### `e is dynamic` pure null check
|
||||
|
||||
We warn that doing `e is dynamic` is equivalent to `e is object`, but `e is object` is a pure
|
||||
null check, while `e is dynamic` is not. Should we make `is dynamic` a pure null check for
|
||||
consistency?
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Yes.
|
||||
|
||||
### Target-typing `?:`
|
||||
|
||||
The simplest example where we have a breaking change is
|
||||
|
||||
```C#
|
||||
void M(short)
|
||||
void M(long)
|
||||
M(b ? 1 : 2)
|
||||
```
|
||||
|
||||
Previously this would choose `long`, because the expressions are effectively typed separately,
|
||||
and when inferring `1 : 2` we would choose `int`, and then rule out `short` during overload
|
||||
resolution as invalid.
|
||||
|
||||
Target-typing converts each arm in turn to find the best possible type, selecting `short` instead
|
||||
of `long`. That means the first overload is selected instead with target-typing.
|
||||
|
||||
It's hard to easily avoid this breaking change. The obvious mechanism, running overload
|
||||
resolution twice, is problematic because having multiple arguments with `conditional` expressions
|
||||
produces exponential growth in the number of passes of overload resolution.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's talk about this again in a separate meeting. Since whatever we choose here will probably
|
||||
be the final decision (any further changes will be breaking), we want to be sure we're making the
|
||||
best choice.
|
||||
|
||||
### Inferred type of an `or` pattern is the common type of two inferred types
|
||||
|
||||
```C#
|
||||
object o = 1;
|
||||
if (o is (1 or 3L) and var x)
|
||||
// what is the type of `x`?
|
||||
```
|
||||
|
||||
The problem here is that the existing common type algorithm allows conversion that are forbidden
|
||||
in pattern matching. In the above case we would choose `long`, because `3L` is a long, and `1`
|
||||
can be converted to `long`. However, in pattern matching the widening conversion from `int` to
|
||||
`long` is illegal.
|
||||
|
||||
The proposal is to narrow the set of conversions only to implicit reference conversions or
|
||||
boxing conversions. Why this could be useful is the example
|
||||
|
||||
```C#
|
||||
class B { }
|
||||
class C : B { }
|
||||
if (o is (B or C) and var x)
|
||||
// x is `B`
|
||||
```
|
||||
|
||||
A follow-up question is about ordering. The proposed rules only infer types mentioned in
|
||||
the checks, meaning that the following
|
||||
|
||||
```C#
|
||||
if (o is (Derived1 or Derived2 or Base { ... }) and var x)
|
||||
```
|
||||
|
||||
looks like this today
|
||||
|
||||
```C#
|
||||
((Derived1 or Derived2) or Base)
|
||||
|
||||
-> ((object) or Base)
|
||||
|
||||
-> (object)
|
||||
```
|
||||
|
||||
On the other hand, if the example were parameterized in the opposite way,
|
||||
|
||||
```C#
|
||||
(Derived1 or (Derived2 or Base))
|
||||
|
||||
-> (Derived1 or (Base))
|
||||
|
||||
-> (Base)
|
||||
```
|
||||
|
||||
So the ordering seemingly matters if the `or` pattern is a simple binary expression.
|
||||
|
||||
The first question is if
|
||||
|
||||
```C#
|
||||
if (o is (Derived1 or Derived2 or Base { ... }))
|
||||
```
|
||||
|
||||
should produce `Base`, and the second question is if parenthesizing affects this, e.g. explicitly saying
|
||||
|
||||
```C#
|
||||
if (o is ((Derived1 or Derived2) or Base { ... }))
|
||||
```
|
||||
|
||||
produces `object`.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We're not seeing a lot of scenarios that depend on these features, but not doing it feels like
|
||||
leaving information on the table. Functionally, the compiler can infer a stronger type, so why
|
||||
not do so? The proposed modified common type algorithm is accepted.
|
||||
|
||||
We also think that type narrowing for the `or` operation should use all the `or` operations
|
||||
in a series as the arguments to the common type algorithm.
|
||||
|
||||
### Module Initializers
|
||||
|
||||
Proposal: https://github.com/RikkiGibson/csharplang/blob/module-initializers/proposals/module-initializers.md
|
||||
|
||||
There are a few places where module initializers today. The most common is a shared resource
|
||||
that is used by multiple types, but the program would like to initialize the shared resource
|
||||
before the static constructors of the types are run.
|
||||
|
||||
The question for the implementation is to how to indicate where the code for the module
|
||||
initializer will go, and how the compiler will recognize it.
|
||||
|
||||
The proposal provides a mechanism to identify the module initializer via an attribute on the module
|
||||
that points to a type, and type contains a static constructor that acts as the module initializer.
|
||||
From a conceptual level, this seems more complicated than necessary. Can we put the attribute on the
|
||||
type or, even the method, that holds the code for the module initializer?
|
||||
|
||||
If we go that route, why require the code be in the static constructor at all? Can any static method
|
||||
be a target? One reason why we may prefer a static constructor is that if the emitted module initializer
|
||||
calls the target static constructor method, it's easy to insert code before that method is invoked
|
||||
by writing a static constructor for the containing type. On the other hand, even static constructor
|
||||
can have code inserted before them, for example with static field initializers.
|
||||
|
||||
The last question is whether to allow only one module initializer method, or allow multiple and specify
|
||||
that the compiler will call them in a stable, but implementation-defined order.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Let's let any static method be a module initializer, and mark that method using a well-known attribute.
|
||||
We'll also allow multiple module initializer methods, and they will each be called in a reserved, but
|
||||
deterministic order.
|
|
@ -1,77 +0,0 @@
|
|||
# C# Language Design for April 13, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Roadmap for records
|
||||
2. Init-only properties
|
||||
|
||||
|
||||
# Roadmap for records
|
||||
|
||||
We want to break down the records feature in a way that gets incremental steps out to partner teams and users sooner, and lets us iterate based on feedback. So what order should we do things in?
|
||||
|
||||
Two demanding aspects of records that are certainly important but can probably be done later are:
|
||||
|
||||
1. Primary constructors (allowing positional parameters directly on record types)
|
||||
2. Inheritance to and from record types
|
||||
|
||||
So a proposal is to split those off in the first iteration of implementation.
|
||||
|
||||
## Decision
|
||||
|
||||
We do need to get those right to ultimately ship the feature! However, we want to start by building a version of records that:
|
||||
* Is nominal only (no primary constructor)
|
||||
* Inherits from object and can't be inherited from
|
||||
* Special-cases `with` expressions to `With` methods rather than rely on a "Factory" feature
|
||||
* Fully implements value equality
|
||||
|
||||
For this to be useful we need the init-only property feature at the same time as well, so that there is *some* way to create and initialize immutable records.
|
||||
|
||||
In subsequent iterations we will
|
||||
* Add support for inheritance to records
|
||||
* Add primary constructors to records
|
||||
* Generalize the `With` implementation to use factories
|
||||
* Decide on and embrace defaults (`public` by default?) and abbreviations
|
||||
|
||||
On parallel tracks we will work on:
|
||||
* Factory methods
|
||||
* Validators?
|
||||
* Mandatory properties?
|
||||
* Primary constructors as a general feature?
|
||||
|
||||
|
||||
# Init-only members
|
||||
|
||||
Init-only properties are the most urgent separate feature to nail down, as the experience of even the first iteration of records depends on them. There are a couple of design decisions still left open; we address them here.
|
||||
|
||||
## Should we have init-only fields?
|
||||
|
||||
Readonly fields today can only be assigned during construction, and only through a `this` access within the body of the class that declares the field. As we extend the concept of "initialization time" to cover execution of init-only setters (by object initializers in client code) as well as validators (if we add those to the language), it makes sense that `readonly` fields should be assignable from within those kinds of members as well.
|
||||
|
||||
We would *not* allow readonly fields to be directly assigned in an object initializer, however, as that would undermine the expectation that the class author has full control over how and when they are assigned.
|
||||
|
||||
Allowing init-only properties to assign readonly fields would address most of the init-only scenario: An object initializer can be used to set immutable state on the newly created object. A dedicated init-only form of fields is not needed for that. It probably *does* have valid scenarios (in programming styles where immutable fields are themselves public), but those seem less central. We could wait see if that rises to the importance of a seperate, subsequent feature.
|
||||
|
||||
### Decision
|
||||
|
||||
Let's allow init-only property setters (and validators, if and when we add them) to assign to `readonly` fields of the same object.
|
||||
|
||||
Let's not add init-only fields now. We can consider a new kind of field that can be initialized directly in object initializers later, as a separate feature, if we become convinced that it's worthwhile.
|
||||
|
||||
## Syntax
|
||||
|
||||
Should the setters of init-only properties be called `init` or `init set`? In other words, is `init` a modifier on the `set` accessor, or does it replace it completely?
|
||||
|
||||
Shorter is generally nicer. However, we do foresee a (near?) future where `init` as a modifier could be applied to other members and accessors, to mean that they can only run during initialization (and in return would get privileges such as assigning to readonly members).
|
||||
|
||||
### Decision
|
||||
|
||||
`init` it is. `init` as a modifier is an interesting feature, but we can discuss it separately. If we do, we can consider allowing `init set` as a long form of an init-only setter for regularity when code has `init` modifiers in multiple places. But for now, let's just allow `init` instead of `set`.
|
||||
|
||||
## Init-only indexers
|
||||
|
||||
Indexers also have `set` accessors. Should they also be allowed to declare an `init` accessor instead?
|
||||
|
||||
### Decision
|
||||
|
||||
It makes perfect sense, is somewhat useful, would be more regular in the language, and has straightforward semantics. Let's do it.
|
|
@ -1,74 +0,0 @@
|
|||
# C# Language Design Notes for Apr 15, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Non-void and non-private partial methods
|
||||
2. Top-level programs
|
||||
|
||||
# Non-void, non-private partial methods
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/3301.
|
||||
|
||||
Currently, partial methods are required to return void. They are implicitly private, and cannot have an explicit accessibility. In return for that, calls to a partial method that has a *declaration* but no *definition* can be safely elided by the compiler. Thus, partial methods serve as optional "hooks" or points of extension for generated code, code that is conditionally included based on compilation target, etc.
|
||||
|
||||
With the expected advent of source generators in the C# 9.0 timeframe, there are likely to many scenarios where these restrictions are too limiting. The proposal suggests a different trade-off for partial methods, where they *can* have return values, and *can* have broader accessibility, but in exchange the definition is *mandatory*: An implementation *must* be provided, since calls can't be elided.
|
||||
|
||||
The mandatory aspect can in fact be viewed as a feature. It is a way for one part of the code to *require* another part to be specified, even as they are separated across files and authorship.
|
||||
|
||||
Main concern is that we would need to preserve the "old" semantics for compatibility in cases that are already allowed in C#, and developers may accidentally fall into that case, failing to compel another part to produce an implementation, and having calls elided without wanting to.
|
||||
|
||||
One mitigating factor is that the existing feature doesn't allow you to explicitly say `private` - it has to be implied. So we could say that if `private` is explicitly supplied we are in the new semantics, and the method implementation is required. It's a subtle an non-obvious distinction, but at least it is there.
|
||||
|
||||
Another question is whether we would allow other members to be partial. We would need to work out the syntax in each case: E.g. how do you distinguish a partial property definition from a declaration that implements it as an auto-property?
|
||||
|
||||
## Decision
|
||||
|
||||
Despite the weirdness of distinguishing between implicit and explicit private (the latter requires an implementation, the former does not), we are ok with accepting this wart in the language. The feature extension is valuable, and alternative solutions are distinctly less appetizing.
|
||||
|
||||
On the other hand we are not ready to allow `partial` on other kinds of members. If future scenario bear out a strong need, we will do the design work to hash it out, but we think methods are able to address the vast majority of what's needed.
|
||||
|
||||
# Top-level statements
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/blob/master/proposals/Simple-programs.md
|
||||
|
||||
We took a look at the currently implemented semantics to make sure we are happy with them. A couple of questions came up:
|
||||
|
||||
## Expressions at the end
|
||||
|
||||
Part of the motivation for the feature was to decrease the syntactic distance between C# (.cs) and its scripting dialect (.csx). However, unlike script we still don't allow expressions at the end. For the scripting dialect this is mostly for producing a result in an interactive setting.
|
||||
|
||||
### Decision
|
||||
|
||||
We are ok with this remaining distance, and would prefer not to have a notion of "expression at the end produces result" in C#.
|
||||
|
||||
## Shadow and error
|
||||
|
||||
The proposal puts top-level local variables and functions in scope inside type declarations in the program. However, if they are are used in those places, and error is given.
|
||||
|
||||
### Decision
|
||||
|
||||
This is deliberately there to allow us to do a more general form of top-level functions in the future. We do believe that it protects likely future designs for this.
|
||||
|
||||
## Args
|
||||
|
||||
Currently there is no way to access the `args` array optionally given as input to an explicit `Main` method. Instead you have to make use of existing APIs that have a slightly different behavior (they include the name of the program as the first element), and certainly look different.
|
||||
|
||||
For anyone who uses the APIs in a top level program, they can still trivially move it into an explicit `Main` method at a later point, but going the other way with a `Main` body that uses `args` is not so easy.
|
||||
|
||||
There are ideas to:
|
||||
- add a new API that looks more like `args` (e.g. `Something.Args`) and behaves the same way
|
||||
- add `args` as a magic variable in top level programs (similar to `value` in property setters), on the assumption that 99.9% of `Main` methods use `args` as the parameter name.
|
||||
|
||||
### Decision
|
||||
|
||||
We think this is important to pursue further, but aren't going to hold up the feature for it.
|
||||
|
||||
## Await triggers a different signature
|
||||
|
||||
In the current implementation, the signature of the `Main`-like method generated from the top level program will be different, depending on whether `await` is used or not. If it is, then the signature will include `async Task<...>`, otherwise it won't.
|
||||
|
||||
An alternative would be to always generate a `Task`-based signature, and just suppress the usual warning when no `await`s occur in the body. The choice doesn't affect the user much. The main difference is that with the current design there is no need to reference the `Task` types, and any limitations imposed by the language inside async methods are not in force, unless `await` is used.
|
||||
|
||||
### Decision
|
||||
|
||||
We stick with the current design.
|
|
@ -1,64 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for April 20, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records:
|
||||
|
||||
1. Factory methods
|
||||
|
||||
## Discussion
|
||||
|
||||
The proposal at its core is to allow certain methods to opt-in to certain language semantics
|
||||
that are only currently valid at construction, namely object initializers, collection
|
||||
initializers, and the new `with` expression (although that expression is legal on only certain
|
||||
factory methods).
|
||||
|
||||
Possible extension of the feature: allow initializer forms everywhere, but only allow `init`
|
||||
properties to be initialized when the method is marked with `Factory`. However, almost all uses
|
||||
of this syntax would be a mutation of the receiver, and it may not be clear that the initializer
|
||||
syntax produces a mutation.
|
||||
|
||||
As to whether `null` should be a valid return: most people think no. Since almost all initializer
|
||||
forms dereference the receiver, this is essentially creating a very obscure way of producing
|
||||
exceptions. In addition, all struct values should be permitted, as they are all safe. `default`
|
||||
should be legal if the target type is known to be a struct. We have not considered what the
|
||||
behavior should be for unconstrained generics.
|
||||
|
||||
There also some concerns about the syntactic extensions. First in that this would make `identifier {
|
||||
... }` a valid syntactic form in most situations. This may not be syntactically ambiguous today,
|
||||
but we have a lot of different features, like `block expressions`, which share some syntactic
|
||||
similarity. Even if there is no syntactic ambiguity, some people are still concerned that the feature
|
||||
will be too opaque. One way to remedy this would be to require the `new` keyword for this form as well.
|
||||
So the new syntax would be:
|
||||
|
||||
```C#
|
||||
var s = new Create() { Name = "Andy" };
|
||||
```
|
||||
|
||||
There could be some naming ambiguity here because `Create` could be either a factory method or a
|
||||
type name. We would have to preserve the interpretation as a type here for compatibility.
|
||||
|
||||
There's a broader question of how or if we'd like a general initializer feature. There's some
|
||||
question of whether the feature is useful enough to deserve the complexity at all, using any
|
||||
additional syntax. Alternatively, we could embrace the syntax requiring the `new` keyword.
|
||||
|
||||
One important piece of history is that initializers are not meant for mutating existing state,
|
||||
only for mutating new objects. This doesn't necessarily conflict with allowing initializers on
|
||||
any object, but the reason here is not that the language is suggesting using object initializers
|
||||
for arbitrary mutation, but that convention alone is good enough to promote use on "new" objects
|
||||
only.
|
||||
|
||||
Regardless of the extensions of the feature, we certainly need to implement something for
|
||||
records. The core feature requirement here is for the `with` expression, which needs to assign to
|
||||
`init` fields. We can head two directions: special case the `Clone` method, or build a more general
|
||||
feature. This is a spectrum, where one end may be a new syntactic form specific to just the Clone
|
||||
method, and the other end could be a `Factory` attribute that could be applied to any method.
|
||||
|
||||
### Conclusion
|
||||
|
||||
Right now we're more concerned with what to do for records. In the meantime, let's not support
|
||||
user-written Clone methods. A Clone method will be generated for a record with an unspeakable type
|
||||
and the SpecialName flag. The `with` expression will look for exactly that method name. We intend
|
||||
to decide for C# 9 how that method will be written in source. We'll consider broader `Factory`
|
||||
scenarios later.
|
|
@ -1,180 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for April 27, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Records: positional
|
||||
|
||||
## Discussion
|
||||
|
||||
The starting point for positional records is how it fits in with potential
|
||||
"primary constructor" syntax. The original proposal for primary constructors
|
||||
allowed the parameters for primary constructors to be visible inside the class:
|
||||
|
||||
```C#
|
||||
class MyClass(int x, int y)
|
||||
{
|
||||
public int P => x + y;
|
||||
}
|
||||
```
|
||||
|
||||
When referenced, `x` and `y` would be like lambda captures. They would be in
|
||||
scope, and if they are captured outside of a constructor, a private backing
|
||||
field would be generated.
|
||||
|
||||
One consequence of this design is that the primary constructor must *always*
|
||||
run. Since the parameters are in scope throughout the entire class, the primary
|
||||
constructor must run to provide the parameters. The proposed way of resolving
|
||||
this is to require all user constructors to call the primary constructor, instead
|
||||
of allowing calls to `base`. The primary constructor itself would be the only
|
||||
constructor allowed (and required) to call `base`.
|
||||
|
||||
This does present a conundrum for positional records. If positional records support
|
||||
the `with` expression, as we intended for all records, they must generate two constructors:
|
||||
a primary constructor and a copy constructor. We previously specified that the copy
|
||||
constructor works off of fields, and is generated as follows
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
protected C(C other)
|
||||
{
|
||||
field1 = other.field1;
|
||||
...
|
||||
fieldN = other.fieldN;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This generated code violates the previous rule: it doesn't call the primary constructors.
|
||||
One way to resolve this would be to change the codegen to delegate to the primary constructor:
|
||||
|
||||
```C#
|
||||
record class Point(int X, int Y)
|
||||
{
|
||||
protected Point(Point other) : Point(other.X, other.Y) { }
|
||||
}
|
||||
```
|
||||
|
||||
This is almost identical, except that the primary constructor may have side-effects, or the
|
||||
property accessors may have side-effects, if user-defined. We had strong opinions against using
|
||||
the accessors before because of this -- we couldn't know if the properties were even
|
||||
auto-properties and whether we were duplicating or even overwriting previous work.
|
||||
|
||||
However, we note that violating the rule for our generated code shouldn't be a problem in
|
||||
practice. Since the new object is a field-wise duplicate of the previous object, if we assume
|
||||
that the previous object is valid, the new object must be as well. All fields which were
|
||||
initialized by the primary constructor _must already be initialized_. Thus, for our code
|
||||
generation it's both correct and safer to keep our old strategy. For user-written constructors
|
||||
we can require that they call the primary constructor, but because the user owns the type, they
|
||||
should be able to provide safe codegen. In contrast, because the compiler doesn't know the full
|
||||
semantics of the user type, we have to be more cautious in our code generation.
|
||||
|
||||
This doesn't really contradict with our goal of making a record representable as a regular class.
|
||||
A mostly-identical version can be constructed via chaining as described above. The only
|
||||
difference is in property side effects, which the compiler itself can’t promise is identical, but
|
||||
if it were written in source then the user could author their constructor to behave similarly.
|
||||
|
||||
Property side-effects have an established history of being flexible in the language and the
|
||||
tooling. Property pattern matching doesn't define the order in which property side effects are
|
||||
evaluated, doesn't promise that they even will be evaluated if they’re not necessary to determine
|
||||
whether the pattern matches, and doesn't promise that the ordering will be stable. Similarly, the
|
||||
debugger auto-evaluates properties in the watch window, regardless of side effects, and the
|
||||
default behavior is to step over them when single stepping. The .NET design guidelines also
|
||||
specifically recommend to not have observable side effects in property evaluation.
|
||||
|
||||
We now have a general proposal for both how positional records work, and how primary constructors
|
||||
work.
|
||||
|
||||
Primary constructors work like capturing. The parameters from the primary constructor are visible
|
||||
in the body of the class. In field initializers and possibly a primary constructor body, they are
|
||||
non-capturing, namely that use of the parameter does not capture to any fields. Everywhere else
|
||||
in the class, the parameters are captured and create a private backing field.
|
||||
|
||||
Positional records work like primary constructors, except that they also generate public
|
||||
init-only properties for each positional record parameter, if a member with the same signature
|
||||
does not already exist. This means that in field initializers and the primary constructor body,
|
||||
the parameter name is in scope and shadows the properties, while in other methods the parameter
|
||||
name is not in scope. In addition, the generated constructor will initialize all properties with
|
||||
the same names as the positional record parameters to the parameter values, unless the
|
||||
corresponding members are not writeable.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The above proposals are accepted. Both positional records and primary constructors are accepted
|
||||
with the above restrictions. In source, all non-primary constructors in a type with a primary
|
||||
constructor must call the primary constructor. The generated copy constructor will not be
|
||||
required to follow this rule, instead doing a field-wise copy. The exact details of the scoping
|
||||
rules, including whether primary constructors have parameters that are in scope everywhere, or
|
||||
simply generate a field that is in scope and shadows the parameter, is an open issue.
|
||||
|
||||
### Primary constructor bodies and validators
|
||||
|
||||
We do have a problem with some syntactic overlap. We previously proposed that our original
|
||||
syntax for primary constructor bodies could be the syntax for a validator. However, there
|
||||
are reasons why you may want to write both. For instance, constructors are a good way to
|
||||
provide default values for init-only properties that may be overwritten later. Validators
|
||||
are still useful for ensuring that the state of the object is legal after the init-only.
|
||||
In that case we need two syntaxes that can be composed. The proposal is
|
||||
|
||||
```C#
|
||||
class TypeName(int X, int Y)
|
||||
{
|
||||
public TypeName
|
||||
{
|
||||
// constructor
|
||||
}
|
||||
|
||||
init
|
||||
{
|
||||
// validator
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To mirror the keyword used for init-only properties, we could use the `init` keyword
|
||||
instead. This would also hint that validators aren't *only* for validating the state,
|
||||
they can also set init-only properties themselves. To that end, we have a tentative name:
|
||||
final initializers.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Accepted. `type-name '{' ... '}'` will refer to a primary-constructor-body and `init '{' ... '}'` is
|
||||
the new "validator"/"final initializer" syntax. No decisions on semantics.
|
||||
|
||||
### Primary constructor base calls
|
||||
|
||||
Given that we have accepted the following syntax for primary constructors and primary constructor bodies,
|
||||
|
||||
```C#
|
||||
class TypeName(int X, int Y)
|
||||
{
|
||||
public TypeName
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
how should we express the mandatory base call? We have two clear options:
|
||||
|
||||
```C#
|
||||
class TypeName(int X, int Y) : BaseType(X, Y);
|
||||
|
||||
class TypeName(int X, int Y) : BaseType
|
||||
{
|
||||
public TypeName : base(X, Y) { }
|
||||
}
|
||||
```
|
||||
|
||||
We mostly like both. The first syntax feels very simple and it effectively moves the "entire"
|
||||
constructor signature up to the type declaration, instead of just the parameter list. However,
|
||||
we don't think that class members would be in scope in the argument list for this base call
|
||||
and there are some rare cases where arguments to base calls may involve calls to static private
|
||||
helper methods in the class. Because of that we think the second syntax is more versatile and
|
||||
reflects the full spectrum of options available in classes today.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Both syntaxes are accepted. If prioritization is needed, the base specification on the primary
|
||||
constructor body is preferred.
|
|
@ -1,95 +0,0 @@
|
|||
# C# Language Design for May 4, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Design review feedback
|
||||
|
||||
## Discussion
|
||||
|
||||
We had a design review on 2020-04-29 to bring our latest designs to the full review team and get
|
||||
feedback. Today we went over the feedback and how it would affect our design.
|
||||
|
||||
### Final initializers
|
||||
|
||||
- Design review said it was very complicated, when do I use an initializer vs a constructor?
|
||||
|
||||
A possible fix would be to try to run initializers *before* constructors, instead of after. The
|
||||
main problem is that this is not where object initializers (using setters) run today. It would be
|
||||
very distasteful to have `init-only` setters run at a different time from regular setters, and
|
||||
worse to subtly run the setters at a different time just because of the presence of a different
|
||||
`init-only` field.
|
||||
|
||||
This is a difficult piece of feedback to reconcile, because it doesn't present a clear direction.
|
||||
However, we're not sure we need to finish the design for final initializers now. We still think
|
||||
the scenarios are useful, but there are many scenarios which don't rely on those semantics. One
|
||||
of the most important scenarios that we were worried about was how to copy a type that had
|
||||
private fields that should not be copied. One proposal was to write a final initializer which
|
||||
either resets certain fields, or `throw`s if the state is invalid. Our proposed alternative for
|
||||
this situation is to write your own copy constructor, which sets up the appropriate state for the
|
||||
copy.
|
||||
|
||||
However, final initializers do address a significant shortfall in existing scenarios, namely that
|
||||
there's no way to validate a whole object in a property setter (or initter). In that sense we do
|
||||
have many existing issues, separate from our records designs, which would be addressable with the
|
||||
feature. There is also no way to validate an object after a `with` expression since necessarily.
|
||||
|
||||
### Factory methods
|
||||
|
||||
The review team agreed about the necessity of "factory" semantics in the `with` expression, namely
|
||||
that the with expression essentially requires a `virtual` Clone method to work correctly through
|
||||
inheritance, but was not convinced that the feature was generally useful.
|
||||
|
||||
We're also not convinced that it's generally useful, but limiting `with` to only be usable on a
|
||||
record is a significant change from where we were before, where records are currently fully
|
||||
representable as regular classes.
|
||||
|
||||
We need to consider if we are willing to live with this limitation, or need a way of specifying
|
||||
the appropriate `Clone` method in source.
|
||||
|
||||
### Structs as records
|
||||
|
||||
Can every struct be a record automatically? We don't need a `Clone` method, because structs
|
||||
already copy themselves and they already implement value equality (albeit sometimes
|
||||
inefficiently). If we take this stance, would we want to explicitly design records as "struct
|
||||
behavior for classes?" If that's true, we would seek to use the behavior of structs as a template
|
||||
for records.
|
||||
|
||||
### Positional records
|
||||
|
||||
The feedback was negative about making a primary constructor parameters different from positional
|
||||
record parameters. The proposal during the design meeting was that primary constructors would see
|
||||
parameters as "captured" in the scope of the class, while records would generate public
|
||||
properties for each parameter. This is a big semantic divergence, as expressions like
|
||||
`this.parameter` would be legal in the body of a positional record, but illegal in the body of a
|
||||
class with a primary constructor. One way of shrinking the semantic gap would be to always
|
||||
generate members based on primary constructor parameters, but in regular classes those members
|
||||
would be private fields, while in records they would be public init-only properties. Even this
|
||||
semantic difference was perceived as too inconsistent.
|
||||
|
||||
We have two proposals to unify the behavior inside and outside of records. On one end, we could
|
||||
try to view primary constructors as a syntactic space to contain more elements. By default,
|
||||
primary constructors would be simple parameters, which could be closed over in the class body. By
|
||||
allowing member syntax in the parameter list, the user would have more control over the
|
||||
declaration. For instance,
|
||||
|
||||
```C#
|
||||
public class Person(
|
||||
public string Name { get; init; }
|
||||
);
|
||||
```
|
||||
|
||||
would generate a public property named `Name` instead of simply a parameter and the property
|
||||
would be implicitly assigned in the constructor.
|
||||
|
||||
On the other hand, we could _always_ make public properties, abandoning the idea of
|
||||
primary-constructor-parameters-as-closures. In this formulation,
|
||||
|
||||
```C#
|
||||
class C(int X, int Y);
|
||||
```
|
||||
|
||||
would generate two properties, X and Y. If this is made into a record e.g., `data class C(int X,
|
||||
int Y)`, then the same record members would be synthesized as in a nominal record.
|
||||
|
||||
We did not settle on a conclusion, but have a rough sense that having a primary constructor
|
||||
always generate properties is preferred.
|
|
@ -1,79 +0,0 @@
|
|||
# C# LDM for May 6, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `if (e is not int i)`
|
||||
2. Target-typed conditional
|
||||
3. Extension GetEnumerator
|
||||
4. `args` in top-level programs
|
||||
|
||||
## Discussion
|
||||
|
||||
### `if (e is not int i)`
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3369
|
||||
|
||||
There are broader features that we'd to consider here as well, for instance allowing
|
||||
some declarations below `or` patterns. However, this should be compatible with broader
|
||||
changes and is easy to implement right now.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Accepted for C# 9. Further elaborations will be considered, assuming the schedule could
|
||||
accept it.
|
||||
|
||||
### Target-typed conditional
|
||||
|
||||
We still unfortunately have a breaking change here with
|
||||
|
||||
```C#
|
||||
M(b ? 1 : 2, 1); // calls M(long, long) without this feature; ambiguous without this feature
|
||||
|
||||
M(short, short);
|
||||
M(long, long);
|
||||
```
|
||||
|
||||
As always, breaking changes are very worrying, unless we are confident that almost no real-world
|
||||
code would be broken. If the breaking change results in an ambiguity instead of silent different
|
||||
codegen, that is substantially better, as people would at least know that the compiler changed
|
||||
behavior. At the moment, we only think that this change could result in new ambiguities, not
|
||||
different behavior.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll do some more investigation, try to find code that would be broken, and see if we can accept
|
||||
the change.
|
||||
|
||||
### Extension GetEnumerator
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/43147
|
||||
|
||||
Conclusions:
|
||||
|
||||
No objections to the proposals as written.
|
||||
|
||||
### `args` in Top-Level programs
|
||||
|
||||
If the top-level statements are logically inside a `Main` method, it would be very useful to have
|
||||
access to the command line arguments for the program. You can access these via
|
||||
`Environment.GetCommandLineArgs()`, but it's unfortunate that this is both different from the
|
||||
APIs in Main, and `Environment.GetCommandLineArgs()` includes the program name, and `args` in
|
||||
Main does not.
|
||||
|
||||
If we want to do something, we could have a magic variable named `args` (similar to `value` in
|
||||
setters) or a property in the framework called `Args` (e.g. `Environment.Args`).
|
||||
|
||||
In favor of the property, fewer language-level changes means fewer things that people have to
|
||||
learn.
|
||||
|
||||
In favor of the `args` magic variable, it's simpler to use than a property (since the property
|
||||
would either have to qualified with a type name, or a `using static` would have to be added) and
|
||||
a language feature for the inputs (command line args) mirrors the language feature for the output
|
||||
(returning an `int` that turns into the process exit code).
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll go with the `args` magic variable. We still need to decide on the scope: either equivalent
|
||||
to top-level locals, which are visible in all files but inaccessible, or only in scope in
|
||||
top-level statements. If we make it visible in all files we would only add the variable if there
|
||||
is at least one top-level statement in the program.
|
|
@ -1,107 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for May 11, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records
|
||||
|
||||
## Discussion
|
||||
|
||||
Today we tried to resolve some of the biggest questions about records,
|
||||
namely how to unify the semantics of nominal records and positional
|
||||
records, and what are the key scenarios that we are trying to resolve
|
||||
with records.
|
||||
|
||||
The main inconsistency is that the members `record class Person(string FirstName, string
|
||||
LastName)` are very different from the members in `class Person(string firstName, string
|
||||
lastName)`. One way of resolving this is to unify the meaning of the declaration in the direction
|
||||
of primary constructors. In this variant, the parameters of a primary constructor always capture
|
||||
items by default.
|
||||
|
||||
To produce public init-only properties like we were exploring, we would require an extra
|
||||
keyword, `data`, that could be generalizable. So a record which has two public init-only
|
||||
members would be written
|
||||
|
||||
```C#
|
||||
record Person(data string FirstName, data string LastName);
|
||||
```
|
||||
|
||||
This would allow a generalizable `data` keyword that could be applied even in regular
|
||||
classes, e.g.
|
||||
|
||||
```C#
|
||||
class Person
|
||||
{
|
||||
data string FirstName;
|
||||
data string LastName;
|
||||
public Person(string first, string last)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The worry here is that we're harming an essential motivation for records, namely a short syntax
|
||||
for immutable data types. In the above syntax, `record` alone does not mean immutable data type,
|
||||
but instead only value equality and non-destructive mutation. A problem with this is that value
|
||||
equality is dangerous for mutable classes, since the hash code can change after being added to a
|
||||
dictionary. This was why we were previously cautious about adding a general feature for value
|
||||
equality. One option to discourage misuse would be to provide a warning for any non-immutable
|
||||
member in a record class.
|
||||
|
||||
The other problem is, frankly, it's not that short. Aside from some duplication of intent by
|
||||
requiring both `record` and `data` modifiers, it also requires applying the `data` modifier to
|
||||
each member, so the overhead grows larger as the type does.
|
||||
|
||||
Alternatively, we could go in the complete opposite direction: limit customizability by making
|
||||
records all about public immutable data.
|
||||
|
||||
For instance, nominal records could also have syntax abbreviation
|
||||
|
||||
```C#
|
||||
record Person { string FirstName; string LastName; }
|
||||
```
|
||||
|
||||
and we could avoid confusion by prohibiting other members entirely.
|
||||
|
||||
This would look at lot more like positional records, e.g.
|
||||
|
||||
```C#
|
||||
record Person(string FirstName, string LastName);
|
||||
```
|
||||
|
||||
and we could introduce further restrictions on those by also disallowing other members
|
||||
in the body, or even disallowing primary constructors entirely.
|
||||
|
||||
Disallowing all members inside of records is draconian, but not entirely without precedence.
|
||||
Enums work the same way in C# and members are added via extensions methods. That's not a ringing
|
||||
endorsement since we've considered proposals for allowing members in enums before, but it also
|
||||
doesn't put it outside the realm of possibility for C#, especially in earlier forms.
|
||||
|
||||
The main drawback of the simplest form is the risk that we might have trouble evolving the
|
||||
feature to fit all circumstances. If we wanted to allow a user to define private fields, the
|
||||
syntax with no accessibility modifier now means "public init-only property" so we might not
|
||||
be able to add support for private fields at all, or we might have to use a syntactic distinction
|
||||
that requires a `private` accessibility, which is a subtle change.
|
||||
|
||||
### Conclusion
|
||||
|
||||
We largely prefer the short syntax for records. A nominal record would look like
|
||||
|
||||
```C#
|
||||
record class Person { string FirstName; string LastName; }
|
||||
```
|
||||
|
||||
This would create a class with public `init-only` properties named `FirstName`
|
||||
and `LastName`, along with equality and non-destructive mutation methods.
|
||||
|
||||
Similarly,
|
||||
|
||||
```C#
|
||||
record class Person(string FirstName, string LastName);
|
||||
```
|
||||
|
||||
would create a class with all of the above, but also a constructor and Deconstruct.
|
||||
|
||||
We have yet to confirm whether `record` disallows private fields entirely, or if it
|
||||
just changes the default accessibility.
|
|
@ -1,147 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for May 27, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Records -- syntax
|
||||
|
||||
## Discussion
|
||||
|
||||
### Syntax Questions
|
||||
|
||||
We got significant feedback that `record` is a better name than `data` for indicating
|
||||
a `record`. There are two syntaxes we've been considering here:
|
||||
|
||||
1. `record class Person { ... }`
|
||||
2. `record Person { ... }`
|
||||
|
||||
The main difference here is that (2) has less obvious space for a `struct` token, which
|
||||
raises the question of whether "record structs" are a feature we want to enable.
|
||||
|
||||
There are a couple arguments for why records would be useful for structs. The first is
|
||||
that "value behavior" is a general feature that could be useful for both structs and classes.
|
||||
Value equality exists for structs today, but it is potentially slow in the runtime implementation.
|
||||
|
||||
The second is that the syntax provided for classes is also useful for structs. The positional
|
||||
syntax specifically seems attractive because it has a lot of similarity to tuples and allows a
|
||||
form of "named tuple."
|
||||
|
||||
```C#
|
||||
record struct Point(int X, int Y);
|
||||
```
|
||||
|
||||
On the other hand, we could improve the performance of equality, completely separate from records.
|
||||
For instance, the compiler could add equality methods if they are not present. We also do not necessarily
|
||||
need to address structs first. Since structs already have many features of records they are, in a sense,
|
||||
"less far behind" than classes in record features. It makes sense to concentrate first on classes and
|
||||
consider augmentations for structs in a future update.
|
||||
|
||||
So to return to the original question, we have to decide if we want to move forward with option (2), which
|
||||
is a new form of declaration. Notably, this is a breaking change for certain scenarios e.g.,
|
||||
|
||||
```C#
|
||||
record M(int X, int Y) { ... }
|
||||
|
||||
class C
|
||||
{
|
||||
record M(int X, int Y) { ... }
|
||||
partial record M(int X, int Y);
|
||||
}
|
||||
```
|
||||
|
||||
All of these are currently method declaration syntax. In C# 9 this would be a record declaration
|
||||
with a positional constructor and a body. Normally we would never consider this kind of change,
|
||||
but since we started shipping with .NET Core we do not automatically upgrade language version
|
||||
unless the target framework is the newest one (i.e., NET 5).
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Do not support structs for now. They already support many features of records and we can add
|
||||
more, time permitting.
|
||||
|
||||
The accepted proposal is that the syntax, `<modifiers> <attributes> 'record'` followed by
|
||||
`identifier` and either '(', '{', or '<' would be contextually parsed as a record declaration
|
||||
only if the language version is C# 9.
|
||||
|
||||
### Short-property syntax
|
||||
|
||||
We previously agreed that, to unify the syntax forms in the positional and nominal declaration, we
|
||||
would allow fields in nominal records with no modifiers to instead be interpreted as public auto-properties.
|
||||
After looking at feedback and exploring some of the related issues, we've decided that's not the best approach.
|
||||
|
||||
There are a few proposals on the table:
|
||||
|
||||
1. Leave positional records the same, do not provide special syntax for nominal records.
|
||||
|
||||
```C#
|
||||
public record Point(int X, int Y);
|
||||
public record Point
|
||||
{
|
||||
public int X { get; init; }
|
||||
public int Y { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
2. Unify the declaration forms in favor of nominal records, allowing property declarations in the
|
||||
record parameter list
|
||||
|
||||
```C#
|
||||
public record Point(
|
||||
public int X { get; init; },
|
||||
public int Y { get; init; }
|
||||
)
|
||||
public record Point
|
||||
{
|
||||
public int X { get; init; },
|
||||
public int Y { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
3. Keep positional records the same, provide a new modifier (e.g., `data` or `init`) for members
|
||||
which means "public init-only property"
|
||||
|
||||
```C#
|
||||
public record Point(int X, int Y);
|
||||
public record Point
|
||||
{
|
||||
data int X;
|
||||
data int Y;
|
||||
}
|
||||
```
|
||||
|
||||
4. Provide the new modifier from (3), and require it in both types of records
|
||||
|
||||
```C#
|
||||
public record Point(data int X, data int Y);
|
||||
public record Point
|
||||
{
|
||||
data int X;
|
||||
data int Y;
|
||||
}
|
||||
```
|
||||
|
||||
After discussion, we prefer (3). Positional records already seem to have enough syntactic distinction and
|
||||
the `data` keywords seem superfluous in this position. It also makes the shortest syntax form match up
|
||||
with the most common use case.
|
||||
|
||||
However, we do think some further keyword is necessary for nominal records. Looking too much like existing
|
||||
fields seems like it would be too confusing, especially if we want to also allow fields in records.
|
||||
|
||||
Instead, we're considering leaving positional records to generate public auto-properties by default, partly
|
||||
because they are already a significantly different syntax that cannot be confused with existing language
|
||||
constructs, and providing a new mechanism for positional records.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Keep positional records the same, provide a `data` modifier for fields which means "public
|
||||
init-only property"
|
||||
|
||||
```C#
|
||||
public record Point(int X, int Y);
|
||||
public record Point
|
||||
{
|
||||
data int X;
|
||||
data int Y;
|
||||
}
|
||||
```
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
|
||||
# C# Language Design for June 1, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records:
|
||||
|
||||
1. Base call syntax
|
||||
2. Synthesizing positional record members and assignments
|
||||
3. Record equality through inheritance
|
||||
|
||||
## Discussion
|
||||
|
||||
### Record base call syntax
|
||||
|
||||
We'd like to reconsider adding a base call syntax to a record declaration, i.e.
|
||||
|
||||
```antlr
|
||||
record_base
|
||||
: ':' class_type argument_list?
|
||||
| ':' interface_type_list
|
||||
| ':' class_type argument_list? interface_type_list
|
||||
;
|
||||
```
|
||||
|
||||
The main question is how the scoping of the parameters from the record positional
|
||||
constructor interact with the base call syntax and the record body.
|
||||
|
||||
We would definitely like the parameters to be in scope inside the base call. For the record body,
|
||||
it's proposed that the parameters of the primary constructor are in scope for initializers, and
|
||||
the primary constructor body (if we later accept a proposal for such syntax). The parameters
|
||||
shadow any members of the same name. The parameters are not in scope outside of these locations.
|
||||
|
||||
To unify the scoping behavior between the base call and the body, we propose that members of the
|
||||
body are also in scope in the base call syntax. Instance members would be an error in these locations
|
||||
(similar to how instance members are in scope in initializers today, but an error to use), but
|
||||
the parameters of the positional record constructor would be in scope and useable. Static members
|
||||
would also be useable, similar to how base calls work in ordinary constructors today.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The above proposals are accepted.
|
||||
|
||||
### Synthesized positional record members
|
||||
|
||||
A follow-up question is how to do generation for auto-generated positional properties. We need
|
||||
to decide both 1) when we want to synthesize positional members and 2) when we want to initialize
|
||||
the corresponding members. The affect is most clearly visible in the example below, where the
|
||||
initialization order will affect what values are visible at various times during construction,
|
||||
namely whether the synthesized properties are initialized before or after the `base` call.
|
||||
|
||||
```C#
|
||||
record Person(string FirstName, string LastName)
|
||||
{
|
||||
public string Fullname => $"{FirstName} {LastName}";
|
||||
public override string ToString() => $"{FirstName} {LastName}";
|
||||
}
|
||||
|
||||
record Student(string FirstName, string LastName, int Id)
|
||||
: Person(FirstName, LastName)
|
||||
{
|
||||
public override string ToString() => $"{FirstName} {LastName} ({ID})";
|
||||
}
|
||||
```
|
||||
|
||||
First we discussed when to synthesize members, namely when an "existing" member will prevent
|
||||
synthesis. A simple rule is that we synthesize members when there is no accessible, concrete
|
||||
(non-abstract) matching member in the type already, either because it was inherited or because it
|
||||
was declared. The rule for matching is that if the member would be considered identical in signature,
|
||||
or if it would require the `new` keyword in an inheritance scenario, those members would "match." This
|
||||
rule allows us to avoid generating duplicate members for record-record inheritance and also produces the
|
||||
intuition that we should err on the side of not synthesizing members when they could be confused with
|
||||
an existing member.
|
||||
|
||||
Second, we discussed when and in what order assignments were synthesized from positional record
|
||||
parameters to "matching" members. A starting principle is that in record-record inheritance we don't
|
||||
want to duplicate assignment -- the base record will already assign its members. In that case, we could
|
||||
choose to assign only members synthesized or declared in the current record. That would mean
|
||||
|
||||
```C#
|
||||
record R(int X)
|
||||
{
|
||||
public int X { get; init; }
|
||||
}
|
||||
```
|
||||
|
||||
would initialize the `X` property to the value of the constructor parameter even though the property
|
||||
is not compiler synthesized. However, we would have to decide if it is synthesized before or after
|
||||
the `base` call. In essence, the question is how we de-sugar the assignments. Is `record Point(int X, int Y);`
|
||||
equivalent to
|
||||
|
||||
```C#
|
||||
record Point(int X, int Y) : Base
|
||||
{
|
||||
public int X { get; init; } = X;
|
||||
public int Y { get; init; } = Y;
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```C#
|
||||
record Point(int X, int Y) : Base
|
||||
{
|
||||
public int X { get; init; }
|
||||
public int Y { get; init; }
|
||||
public Point
|
||||
{
|
||||
this.X = X;
|
||||
this.Y = Y;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that today property and field initializers are always executed before the `base` call, while
|
||||
statements in the constructor body are executed afterwards and we are disinclined to change that
|
||||
for record initializers.
|
||||
|
||||
Looking at the examples as a whole, we think using the initializer behavior is good -- it's easy
|
||||
to understand and more likely to be correct in the presence of a virtual call in the base class,
|
||||
but it makes things significantly more complicated if we synthesize it even for user-written
|
||||
properties. Is the initializer synthesized even if there's already an initializer on the property?
|
||||
What if the user-written property isn't an auto-property?
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We think it's much clearer if we simplify the rules to only initialize synthesized properties.
|
||||
Effectively, if you replace the synthesized record property, you also have to write the initialization,
|
||||
if you want it. In the case that the property is not already declared, e.g. `record Point(int X, int Y);`
|
||||
the equivalent code is
|
||||
|
||||
```C#
|
||||
record Point(int X, int Y)
|
||||
{
|
||||
public int X { get; init; } = X;
|
||||
public int Y { get; init; } = Y;
|
||||
}
|
||||
```
|
||||
|
||||
### Equality through inheritance
|
||||
|
||||
We have a number of small and large questions about how records work with inheritance.
|
||||
|
||||
Q: What should we do if one of the members which we intend to override, like object.Equals and
|
||||
object.GetHashCode, are sealed?
|
||||
|
||||
A: Error. This is effectively outside of the scope of automatic generation.
|
||||
|
||||
Q: Should we generate a strongly-typed Equals (i.e., `bool Equals(T)`) for each record declaration? What
|
||||
about implementing `IEquatable<T>`?
|
||||
|
||||
A: Yes. Implementing `IEquatable<T>` is very useful and would require a strongly-typed equals method. We
|
||||
could explicitly implement the method, but we also think this is useful surface area. If we broaden
|
||||
support to structs, this would prevent a boxing conversion, which has a significant performance impact.
|
||||
Even for classes this could avoid extra type checks and dispatch.
|
||||
|
||||
Q: Should each record declaration re-implement equality from scratch? Or should we attempt to dispatch
|
||||
to base implementations of equality?
|
||||
|
||||
A: For the first record in a type hierarchy, we should define equality based on all the accessible fields,
|
||||
including inherited ones, in the record. For records inheriting from a class with an existing
|
||||
`EqualityContract`, we should assume that it implements our contract appropriately, and delegate comparing
|
||||
the EqualityContract itself and the base fields to the base class.
|
|
@ -1,41 +0,0 @@
|
|||
|
||||
# C# LDM for June 10, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. "Roles"
|
||||
|
||||
## Discussion
|
||||
|
||||
Exploration of previous proposal: #1711
|
||||
|
||||
This is a topic that we've explored before which we're reviving for further consideration and discussion.
|
||||
|
||||
We have a "role" proposal, but it's more of a starting point for a final design. There are a number of
|
||||
different problems we can presumably solve here, but it seems like we have some intersecting features that
|
||||
might address multiple problems simultaneously.
|
||||
|
||||
There are many tradeoffs to consider in these designs. One of the most well-known is sometimes called
|
||||
"incoherence," where the ability to implement an interface in two ways on the same type effectively causes
|
||||
the two implementations to "cross" each other in ways that can be hard to predict. For instance, if two
|
||||
people implemented `IEquatable<T>` on the same third-party type, and both added it to a dictionary, if they
|
||||
used different `GetHashCode` implementations then the same member could be added twice, and each consumer
|
||||
wouldn't see the implementation used by other consumers.
|
||||
|
||||
Another tradeoff is the ability to use the role as a type, namely refer to it in a type position. This
|
||||
is often desirable, but has some tradeoffs in type equivalence (see SML modules for alternative notions
|
||||
of type equivalence through functors).
|
||||
|
||||
The Roles proposal as a whole seems very powerful, but there are many big questions here. The biggest,
|
||||
most pressing question is: what problems do we think are the most important and how big a feature do
|
||||
we need to address them? Providing a way to abstract over different numeric abstractions is a concrete
|
||||
scenario, but it may not need the fully generalized mechanism. Allowing existing types to conform
|
||||
to an abstract after definition is also powerful and has many possible use cases, but how flexible
|
||||
do we need to make that mechanism? Can it only be used in generics? Can you implement abstractions
|
||||
defined in other compilations on types defined in other compilations?
|
||||
|
||||
The performance concerns are also very real. We have a few mechanisms for abstraction in the language
|
||||
today, but a lot of those mechanisms come with performance costs like allocation that make them
|
||||
unusable in performance-sensitive scenarios. We would like more zero-cost abstractions if possible,
|
||||
but we're not sure what functionality we could provide in those circumstances and whether the features
|
||||
would fit well into the existing ecosystem.
|
|
@ -1,197 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for June 15, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `modreq` for init accessors
|
||||
|
||||
1. Initializing `readonly` fields in same type
|
||||
|
||||
1. `init` methods
|
||||
|
||||
1. Equality dispatch
|
||||
|
||||
1. Confirming some previous design decisions
|
||||
|
||||
1. `IEnumerable.Current`
|
||||
|
||||
## Discussion
|
||||
|
||||
### `modreq` for init accessors
|
||||
|
||||
We've confirmed that the modreq design for `init` accessors:
|
||||
|
||||
- The modreq type `IsExternalInit` will be present in .NET 5.0, and will be recognized if
|
||||
defined in source
|
||||
|
||||
- The feature will only be fully supported in .NET 5.0
|
||||
|
||||
- Usage of the property (including the getter) will not be possible on older compilers, but
|
||||
if the compiler is upgraded (even if an older framework is being used), the getter will be
|
||||
usable
|
||||
|
||||
### Initializing `readonly` fields in same type
|
||||
|
||||
We previously removed `init` fields from the design proposal because we didn't think it was
|
||||
necessary for the records feature and because we didn't want to allow fields which were
|
||||
declared `readonly` before records to suddenly be settable externally in C# 9, contrary to
|
||||
the author's intent.
|
||||
|
||||
One extension would be to allow `readonly` fields to be set in an object initializer only inside
|
||||
the type. In this case you could still use object initializers to set readonly fields in
|
||||
static factories, but because they would be a part of your type you would always know the intent
|
||||
of the `readonly` modifier. For instance,
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public readonly string? ReadonlyField;
|
||||
|
||||
public static C Create()
|
||||
=> new C() { ReadonlyField = null; };
|
||||
}
|
||||
```
|
||||
|
||||
On the other hand, we may not need a new feature for many of these scenarios. An init-only
|
||||
property with a private `init` accessor behaves similarly.
|
||||
|
||||
```C#
|
||||
class C
|
||||
{
|
||||
public string? ReadonlyProp { get; private init; }
|
||||
|
||||
public static C Create()
|
||||
=> new C() { ReadonlyProp = null; };
|
||||
}
|
||||
```
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We still think `readonly` fields are interesting, but we're not sure of the scenarios yet.
|
||||
Let's keep this on the table, but leave it for a later design meeting.
|
||||
|
||||
### `init` methods
|
||||
|
||||
We previously considered having `init` methods which could modify `readonly` members just
|
||||
like `init` accessors. This could enable scenarios like "immutable collection initializers",
|
||||
where members can be added via an `init` Add method.
|
||||
|
||||
The problem is that the vast majority of immutable collections in the framework today would be
|
||||
unable to adopt this pattern. Collection initializers are hardcoded to use the name `Add` today
|
||||
and almost all the immutable collections already have `Add` methods that are meant for a different
|
||||
purpose -- they return a copy of the collection with the added item.
|
||||
|
||||
If we naively extend `init` to collection initializers most immutable collections wouldn't be able
|
||||
to adopt them because they couldn't make their `Add` methods `init`-only, and no other method name
|
||||
is allowed in a collection initializer. In addition, some types, like ImmutableArrays, would be
|
||||
forced to implement init-only collection initializers very inefficiently, by declaring a new array
|
||||
and copying each item every time `Add` is called.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We're still very interested in the feature, but we need to determine how we can add it in a way
|
||||
that provides a path forward for our existing collections.
|
||||
|
||||
### Equality dispatch
|
||||
|
||||
We have an equality implementation that we think is functional, but we're not sure it's the most
|
||||
efficient implementation. Consider the following chain of types:
|
||||
|
||||
```C#
|
||||
class R1
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R1);
|
||||
public virtual bool Equals(R1 other)
|
||||
=> !(other is null) &&
|
||||
this.EqualityContract == other.EqualityContract
|
||||
/* && compare fields */;
|
||||
}
|
||||
class R2 : R1
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R2);
|
||||
|
||||
public override bool Equals(R1 other)
|
||||
=> Equals(other as R2);
|
||||
|
||||
public virtual bool Equals(R2 other)
|
||||
=> base.Equals((R1)other)
|
||||
/* && compare fields */;
|
||||
}
|
||||
class R3 : R2
|
||||
{
|
||||
public override bool Equals(object other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public override bool Equals(R1 other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public override bool Equals(R2 other)
|
||||
=> Equals(other as R3);
|
||||
|
||||
public virtual bool Equals(R2 other)
|
||||
=> base.Equals((R1)other)
|
||||
/* && compare fields */;
|
||||
}
|
||||
```
|
||||
|
||||
The benefit of the above strategy is that each virtual call goes directly
|
||||
to the appropriate implementation method for the runtime type. The drawback
|
||||
is that we're effectively generating a quadratic number of methods and overrides
|
||||
based on the number of derived records.
|
||||
|
||||
One alternative is that we could not override the Equals methods of anything
|
||||
except our `base`. This would cause more virtual calls to reach the implementation,
|
||||
but reduce the number of overrides.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We need to do a deep dive on this issue and explore all the scenarios. We'll come
|
||||
back once we've outlined all the options and come up with a recommendation.
|
||||
|
||||
### Affirming some previous decisions
|
||||
|
||||
Proposals for copy constructors
|
||||
|
||||
- Do not include initializers (including for user-written copy constructors)
|
||||
|
||||
- Require delegation to a base copy constructor or `object` constructor
|
||||
|
||||
- If the implementation is synthesized, this behavior is synthesized
|
||||
|
||||
Proposal for Deconstruct:
|
||||
|
||||
- Doesn't delegate to a base Deconstruct method
|
||||
|
||||
- Synthesized body is equivalent to a sequence of assignments of member
|
||||
accesses. If any of these assignments would be an error, an error is produced.
|
||||
|
||||
It's also proposed that any members which are either dispatched to in a derived record
|
||||
or expected to be overridden in a derived record will produce an error for synthesized
|
||||
implementations if the required base member is not found. This includes if the base
|
||||
member was not present in the immediate base, but was inherited instead. For some situations
|
||||
this may mean that the user can write a substituted implementation for that synthesized
|
||||
member, but for the copy constructor this effectively forbids record inheritance, since
|
||||
the valid base member must be present even in a user-defined implementation.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
All of the above decisions are upheld.
|
||||
|
||||
### Non-generic IEnumerable
|
||||
|
||||
Currently in the framework `IEnumerable.Current` (the non-generic interface) is annotated to
|
||||
return `object?`. This produces a lot of warnings in legacy code that `foreach` over the result
|
||||
with types like `string`, which is non-nullable. We have two proposals to resolve this:
|
||||
|
||||
- Un-annotate `IEnumerable.Current`. This will keep the member nullable-oblivious and no warnings
|
||||
will be generated, even if the property is called directly
|
||||
|
||||
- Special-case the compiler behavior for `foreach` on `IEnumerable` to suppress nullable warnings
|
||||
when calling `IEnumerable.Current`
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Overall, we prefer un-annotation. Since this interface is essentially legacy, we feel that
|
||||
providing nullable analysis is potentially harmful and rarely beneficial.
|
|
@ -1,122 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for June 17, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Null-suppression & null-conditional operator
|
||||
1. `parameter!` syntax
|
||||
1. `T??`
|
||||
|
||||
## Discussion
|
||||
|
||||
### Null-suppression & null-conditional operator
|
||||
|
||||
Issue #3393
|
||||
|
||||
We generally agree that this is uninintended and unfortunate, essentially a spec bug. The
|
||||
only question is whether to allow `!` at the end of a `?.`, as well as in the "middle". In
|
||||
some sense
|
||||
|
||||
### `parameter!` syntax
|
||||
|
||||
This has been delayed because we haven't been able to agree on the syntax. The main contenders
|
||||
are
|
||||
|
||||
```C#
|
||||
void M(string param!)
|
||||
void M(string param!!)
|
||||
void M(string! param)
|
||||
void M(string !param)
|
||||
void M(checked string param)
|
||||
void M(string param ?? throw)
|
||||
void M(string param is not null)
|
||||
void M(notnull string param)
|
||||
void M(null checked string param)
|
||||
void M(bikeshed string param)
|
||||
void M([NullChecked("Helper")] string param)
|
||||
/* contract precondition forms */
|
||||
void M(string param) Requires.NotNull(param)
|
||||
void M(string param) when param is not null
|
||||
```
|
||||
|
||||
The simplest form, `void M(string param!)` is attractive, but looks very similar to the null
|
||||
suppression operator. The biggest problem is that you can see them as having very different
|
||||
meanings -- `!` in an expression silences nullable warnings, while `!` on a parameter does the
|
||||
opposite, it actually produces an exception on nulls. However, the result of both forms is a
|
||||
value which is treated by the compiler as not-null, so there is a way of seeing them as similar.
|
||||
|
||||
Moving it to the other side of the parameter name, `!param`, would resolve some of the similarity
|
||||
with the null suppression operator, but it also looks a lot like the `not` prefix operator. There's
|
||||
slightly less contradiction in these operators, but it still features a bit of syntactic overloading.
|
||||
|
||||
`string!` has a couple problems, including a suggestion that it's a part of the type (which it would not
|
||||
be), and that sometimes you may want to use the operator on nullable types, like `AssertNotNull` methods.
|
||||
It also wouldn't be usable in simple lambdas without types.
|
||||
|
||||
`checked` suggests integer over/underflow more than nullability.
|
||||
|
||||
`param!!` has some usefulness that we could provide a corresponding expression form -- `!`
|
||||
suppresses null warnings, while `!!` actually checks for null and throws if it is found. On the
|
||||
other hand, it also reads a bit strangely, especially since we're adding a new syntax form
|
||||
instead of trying to reuse some forms we already have. On the other hand, the fact that it's
|
||||
different enough to look different, while also short enough to be used commonly has a lot in
|
||||
favor of it. In general we historically have a bias towards making new things strongly
|
||||
distinguished from existing code, but shortly after introducing the feature we tend to wish that
|
||||
things were less verbose and didn't draw as much attention in the code. On the other hand, the
|
||||
nullable feature has a rule that it should not affect code semantics, while the purpose of this
|
||||
feature is to affect code semantics. `param!!` could be seen as being too similar to other things
|
||||
in the nullable feature, but to some people it also stands out because of the multiple operators.
|
||||
|
||||
We did a brief ranked choice vote and came up with the following ranking, not as definitive, just to
|
||||
measure our current preferences:
|
||||
|
||||
1. `void M(string param!!)`
|
||||
2. `void M(nullcheck string param)`
|
||||
3. `void M([NullChecked(Helper)] string param)`
|
||||
|
||||
Many people don't have strong opinions, so we don't have a clear winner coming out.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We're getting closer to consensus, but we need to discuss this more and consider some of the
|
||||
long-term consequences, as well as impact on other pieces of the language design.
|
||||
|
||||
### `T??`
|
||||
|
||||
Unfortunately there are multiple parsing ambiguities with `T??`:
|
||||
|
||||
```C#
|
||||
(X??, Y?? y) t;
|
||||
using (T?? t = u) { }
|
||||
F((T?? t) => t);
|
||||
```
|
||||
|
||||
This has left us looking back to the original syntax: `T?`. The original reason we rejected this
|
||||
was that there could be confusion that `T?` means "maybe default," so that the type is nullable
|
||||
if it's a reference type, but not nullable if it's a value type.
|
||||
|
||||
If we want to allow the `T?` syntax anyway, we need some syntax to specify that, for overrides,
|
||||
we want the method with no constraints (unconstrained). This is because constraints cannot
|
||||
generally be specified in overrides or explicit implementations, so previously `T?` always meant
|
||||
`Nullable<T>`, but now it may not. We added the `class` constraint for nullable reference types,
|
||||
but neither `T : class` nor `T : struct` help if the `T` is unconstrained. In essence, we need a
|
||||
constraint that means unconstrained. Some options include:
|
||||
|
||||
```C#
|
||||
override void M1<[Unconstrained]T,U>(T? x) // a
|
||||
override void M1<T,U>(T? x) where T: object? // b
|
||||
override void M1<T,U>(T? x) where T: unconstrained // c
|
||||
override void M1<T,U>(T? x) where T: // d
|
||||
override void M1<T,U>(T? x) where T: ? // e
|
||||
override void M1<T,U>(T? x) where T: null // f
|
||||
override void M1<T,U>(T? x) where T: class|struct // g
|
||||
override void M1<T,U>(T? x) where T: class or struct // h
|
||||
override void M1<T,U>(T? x) where T: cluct // joke
|
||||
override void M1<T,U>(T? x) where T: default // i
|
||||
```
|
||||
|
||||
**Conclusion**
|
||||
|
||||
The `default` constraint seems most reasonable. It would only be allowed in overrides and
|
||||
explicit interface implementations, purely for the purpose of differentiating which method
|
||||
is being overridden or implemented. Let's see if there are other problems.
|
|
@ -1,104 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for June 22, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Data properties
|
||||
|
||||
1. Clarifying what's supported in records for C# 9
|
||||
|
||||
- Structs
|
||||
|
||||
- Inheritance with records and classes
|
||||
|
||||
## Discussion
|
||||
|
||||
### `data` properties
|
||||
|
||||
We've been working on an implementation for data properties and have some questions about
|
||||
modifiers, which are currently not allowed.
|
||||
|
||||
There are some modifiers that could be legal, like `new`, `abstract`, `virtual`, and `override`.
|
||||
Some of these, especially `new` and `override` would probably prevent `data` properties from
|
||||
being used at all, since a warning would be generated if there's a matching member in the base
|
||||
that requires either `override` or `new`.
|
||||
|
||||
However, the point of data properties was to be brief. Adding modifiers would potentially cloud
|
||||
the purpose of the feature. The syntactical abbreviation isn't strictly required because the
|
||||
equivalent property can always be written explicitly:
|
||||
|
||||
```C#
|
||||
data int X;
|
||||
|
||||
// versus
|
||||
|
||||
public int X { get; init; }
|
||||
```
|
||||
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Based on where we are in the schedule for C# 9, we probably will not have time to respond to feedback.
|
||||
Therefore, we're planning on putting this feature into preview and not shipping it with C# 9. For now,
|
||||
let's allow the following modifiers: `public`, `new`, `abstract`, `virtual`, and `override`. This will
|
||||
provide an option for allowing `data` properties to interact more smoothly with other constructs in C#,
|
||||
while maintaining some of the simplicity. We'll see how it feels in prototypes to gauge whether we went
|
||||
too far, or not far enough.
|
||||
|
||||
### Struct records
|
||||
|
||||
We're not sure how much space we have for structs in C# 9, but we do think structs are an
|
||||
interesting design point. We previously proposed that structs could support a variety of record
|
||||
behaviors without being a record. One idea would be to automatically implement `IEquatable<T>`
|
||||
for all structs, but this has a lot of potential negative impact for the runtime and the
|
||||
ecosystem: effectively every struct would add a large number of members which could seriously
|
||||
bloat metadata.
|
||||
|
||||
An alternative would be to work with the runtime to try to provide a runtime-generated form of
|
||||
`IEquatable<T>`. If it's cheap enough, we may be able to provide `IEquatable<T>` for structs as
|
||||
a as-necessary addition to all structs. However, this is a potentially very large change. Even
|
||||
small costs could add up, and even small semantic changes could impact someone.
|
||||
|
||||
Overall, the modification of arbitrary struct semantics just seems too risky. Something as simple
|
||||
as testing a struct for an implementation of `IEquatable<T>` could be a breaking change, and with
|
||||
a change this big, it's almost certain to be breaking for someone.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Altering the semantics of all structs as-is is out. We're leaning towards a real "record struct"
|
||||
declaration.
|
||||
|
||||
### Inheritance between records and classes
|
||||
|
||||
We currently have a restriction that records cannot inherit from classes and classes cannot
|
||||
inherit from records. We should confirm that this restriction is acceptable for C# 9.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We definitely see scenarios for expanding this, but we think the restriction is fine for the
|
||||
first version of records. Based on feedback and example scenarios, we can reconsider these
|
||||
restrictions and also the expected semantics.
|
||||
|
||||
### `with` expressions on non-records
|
||||
|
||||
This encompasses:
|
||||
|
||||
1. Finding some way to define a compatible `Clone` for user-defined classes
|
||||
|
||||
1. Anonymous types
|
||||
|
||||
1. Tuples
|
||||
|
||||
Compatibility for `Clone` in user-defined classes is the most difficult of these items because it
|
||||
relies on a new language feature for requiring that a new object is returned from the Clone
|
||||
method.
|
||||
|
||||
Anonymous types are very simple, we can retroactively define them as record types.
|
||||
|
||||
Tuples would be simple to special-case in the language, but it's probably more consistent to
|
||||
decide the semantics for struct records first, and then update the definition of
|
||||
System.ValueTuple to support them.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Anonymous types can be updated at any time, everything else should wait until after C# 9.
|
|
@ -1,188 +0,0 @@
|
|||
# C# Language Design Meeting for June 24th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Confirming Function Pointer Decisions](#Confirming-Function-Pointer-Decisions)
|
||||
1. [Parameter Null Checking](#Parameter-Null-Checking)
|
||||
1. [Interface `static` Member Variance](#Interface-`static`-Member-Variance)
|
||||
1. [Property Enhancements](#Property-Enhancements)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"It feels a little bit like we're playing code golf here"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Confirming Function Pointer Decisions
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/39865#issuecomment-647692516
|
||||
|
||||
There are a few open questions from a previous [LDM](LDM-2020-04-01.md) and a followup email chain
|
||||
that need to be confirmed before they can be implemented. These questions center around calling
|
||||
convention type lookup and how identifiers need to be written in source. The grammar we had roughly
|
||||
proposed after the previous meeting is:
|
||||
|
||||
```antlr
|
||||
func_ptr_calling_convention
|
||||
: 'managed'
|
||||
| 'unmanaged' ('[' func_ptr_callkind ']')?
|
||||
|
||||
func_ptr_callkind
|
||||
: 'CallConvCdecl'
|
||||
| 'CallConvStdcall'
|
||||
| 'CallConvThiscall'
|
||||
| 'CallConvFastcall'
|
||||
| identifier (',' identifier)*
|
||||
```
|
||||
|
||||
##### Calling Convention Lookup
|
||||
|
||||
When attempting to bind the `identifier` used in an unmanaged calling convention, should this follow
|
||||
standard lookup rules, such that the type must be in scope at the current location, or is using a
|
||||
form of special lookup that disregards the types in scope at the current location? The types valid
|
||||
in this location are a very specific set: they must come from the `System.Runtime.CompilerServices`
|
||||
namespace, and the types must have been defined in the same assembly that defines `System.Object`,
|
||||
regardless of the binding strategy used here, so it's really a question of whether the user has to
|
||||
include this namespace in their current scope, adding a bunch of types that they are generally not
|
||||
advised to use directly, and whether they can get an error because they defined their own calling
|
||||
convention.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Given the specificness required here, we will use special name lookup.
|
||||
|
||||
##### Required identifiers
|
||||
|
||||
The previous LDM did not specify the required syntax for the identifiers quite explicitly enough for
|
||||
implementation, and specified that identifiers should be lowercase while also having upper case
|
||||
identifiers in some later examples. The following rules are proposed as the steps the compiler will
|
||||
take to match the identifier to a type:
|
||||
|
||||
1. Prepend `CallConv` onto the identifier. No casemapping is performed.
|
||||
2. Perform special name lookup with that typename in the `System.Runtime.CompilerServices` namespace
|
||||
only considering types that are defined in the core library of the program (the library that defines
|
||||
`System.Object` and has no dependencies itself).
|
||||
|
||||
We also reconsidered the decision from the previous LDM on using lowercase mapping for the identifier
|
||||
names. There is convention for this in other languages: C/C++, for example, use `__cdecl` or similar
|
||||
as their calling convention specifiers, and given that this feature will be used for native interop
|
||||
with libraries doing this it would be nice to have some parity. However, this would introduce several
|
||||
issues with name lookup: existing special name lookup allows us to modify the `identifier` specified
|
||||
in source, but it does not allow us to modify the names of the types we're matching against, which
|
||||
we would need to do here. There is certainly an algorithm that could be specified here, but we overall
|
||||
felt that this was too complicated for what was a split aesthetic preference among members.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
The proposed rules are accepted. As a consquence, the identifier specified in source cannot start
|
||||
with `CallConv` in the name, unless the runtime were to add a type like `CallConvCallConv`.
|
||||
|
||||
### Parameter Null Checking
|
||||
|
||||
We ended the [previous meeting](LDM-2020-06-17.md) on this with two broad camps: support for the `!!`
|
||||
syntax, and support for some kind of keyword. Email discussion over the remainder of the week and
|
||||
polling showed that a clear majority supported the `!!` syntax.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will be moving forward with `!!` as the syntax for parameter null checking:
|
||||
|
||||
```cs
|
||||
public void M(Chitty chitty!!)
|
||||
```
|
||||
|
||||
### Interface `static` Member Variance
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3275
|
||||
|
||||
We considered variance in `static` interface members. Today, for co/contravariant type parameters
|
||||
used in these members, they must follow the full standard rules of variance, leading to some
|
||||
inconsistency with the way that `static` fields are treated vs `static` properties or methods:
|
||||
|
||||
```cs
|
||||
public interface I<out T>
|
||||
{
|
||||
static Task<T> F = Task.FromResult(default(T)); // No problem
|
||||
static Task<T> P => Task.FromResult(default(T)); //CS1961
|
||||
static Task<T> M() => Task.FromResult(default(T)); //CS1961
|
||||
static event EventHandler<T> E; // CS1961
|
||||
}
|
||||
```
|
||||
|
||||
Because these members are `static` and non-virtual, there aren't any safety issues here: you can't
|
||||
derive a looser/more restricted member in some fashion by subtyping the interface and overriding
|
||||
the member. We also considered whether this could potentially interfere with some of the other
|
||||
enhancements we hope to make regarding roles, type classes, and extensions. These should all be
|
||||
fine: we won't be able to retcon the existing static members to be virtual-by-default for interfaces,
|
||||
as that would end up being a breaking change on multiple levels, even without changing the variance
|
||||
behavior here.
|
||||
|
||||
We also considered whether this change could be considered a bug fix on top of C# 8, meaning that
|
||||
users would not have to opt into C# 9 in order to see this behavior. While the change is small and
|
||||
likely very rarely needed, we would still prefer to avoid breaking downlevel compilers.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will allow `static`, non-virtual members in interfaces to treat type parameters as invariant,
|
||||
regardless of their declared variance, and will ship this change in C# 9.
|
||||
|
||||
### Property Enhancements
|
||||
|
||||
In a [previous LDM](#LDM-2020-04-01.md) we started to look at various enhancements we could make
|
||||
to properties in response to customer feedback. Broadly, we feel that these can be addressed by
|
||||
one or more of the following ideas:
|
||||
|
||||
1. Introduce a `field` contextual keyword that allows the user to refer to the backing storage of
|
||||
the property in the getter/setter of that property.
|
||||
* In this proposal, we can consider all properties today as having this `field` keyword. As
|
||||
an optimization, if the user does not refer to the backing field in the property body, we
|
||||
elide emitting of the field, which happens to be the behavior of all full properties today.
|
||||
* Of the proposals, this allows the most brevity for simple scenarios, allowing some lazily-fetched
|
||||
properties to be one-liners.
|
||||
* This proposal does not move the cliff far: if you need to have a type differing from the
|
||||
type of the property, or multiple fields in a single property, then you must fall back to a full
|
||||
property and expose the backing field to the entire class.
|
||||
* There are also questions about the nullability of these backing properties: must they be
|
||||
initialized? Or should we provide a way to declare them as nullable, despite the property
|
||||
itself not being nullable?
|
||||
* There was also concern that this is adding conceptual overhead for not enough gain: education
|
||||
would be needed on when backing fields are elided and when they are not, complicated by the
|
||||
additional backcompat overhead of ensuring that `field` isn't treated as a keyword when it
|
||||
can bind to an existing name.
|
||||
2. Allow field declarations in properties.
|
||||
* Conveniently for this proposal, a scoping set of braces for properties already exists.
|
||||
* This addresses the issue of properties backed by a different storage type: if you have a
|
||||
property that uses a `Lazy<T>` to initialize itself on first access, for example.
|
||||
* This also allows users to declare multiple backing fields, if they want to lock access
|
||||
to the property or combine multiple pieces of information into a single type in the public
|
||||
surface area.
|
||||
* In terms of user education, this is the simplest proposal. Since a set of braces already
|
||||
exists, the education is just "There's a new scope you can put fields in."
|
||||
3. Introduce a delegated property pattern into the language.
|
||||
* There have been a few proposals for this, such as #2657. Other languages have also adopted
|
||||
this type of feature, including Kotlin and Swift.
|
||||
* This is by far the most complex of the proposals, adding new patterns to the language and
|
||||
requiring declaration of a whole new type in order to remove one or possibly 2 fields from
|
||||
a general class scope.
|
||||
* By that same token, however, this is the most broad of the proposals, allowing users to
|
||||
write reusable property implementations that could be abstracted out.
|
||||
|
||||
The LDM broadly viewed these proposals as increasing in scope: the `field` keyword allows the most
|
||||
brief syntax, but forces users off the cliff back to full class-scoped fields immediately if their
|
||||
use case is not having a single backing field of the same type. Meanwhile, property-scoped fields
|
||||
don't allow for and encourage creating reusable helpers, like delegated properties would.
|
||||
|
||||
We also recognize that regardless of what decisions we make today, we're not done in this space.
|
||||
None of these proposals are mutually exclusive, and we can "turn the crank" by introducing one, and
|
||||
then adding more in a future release. There is interest among multiple LDM members in adding some
|
||||
form of reusable delegated properties or property wrappers, and adding one of either the `field`
|
||||
keyword or property-scoped fields does not preclude adding the other in a later release. Further,
|
||||
all of these proposals are early enough that we still have a bunch of design space to work through
|
||||
with them, while designing ahead enough to ensure that we don't add a wart on the language that we
|
||||
will regret in the future.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
A majority of the LDM members would like to start by exploring the property-scoped locals space.
|
||||
We'll start by expanding that proposal with intent to include in C# 10, but will keep the other
|
||||
proposals in mind as we do so.
|
|
@ -1,80 +0,0 @@
|
|||
# C# Language Design Meeting for June 29th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Static interface members](https://github.com/Partydonk/partydonk/issues/1)
|
||||
|
||||
## Procedural Note
|
||||
|
||||
For the past few years, the LDM notes have been compiled by the wonderful Andy Gocke (@agocke).
|
||||
Andy is taking the next step in his career and moving to be the lead of the CLR App Model team
|
||||
as of today, and as such will be moving to LDT emeritus status, joining the ranks of our other
|
||||
former LDT members who form our advisory council. We thank him for all his hard work during his
|
||||
time as notetaker, collating the sometimes inane rambling of the LDM in a set of readable
|
||||
arguments and decisions.
|
||||
|
||||
At this point Fred Silberberg (@333fred) will be the new LDM notetaker. While I'll try to keep
|
||||
up much of the same spirit as Andy, I am not going to try to match his exact style or formatting,
|
||||
so there could be some changes in the exact formatting of the notes. I'll additionally apologize
|
||||
in advance as the notes inevitably end up delayed in the first few weeks as I settle in. And now,
|
||||
on to the meeting notes!
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"So we spent these last 10 years making it so we can come back to this. We actually never forgot,
|
||||
[.NET Core] has all been a ploy to get statics into interfaces."
|
||||
|
||||
## Discussion
|
||||
|
||||
Today @abock and @migueldeicaza presented their work on Partydonk over the past year, bringing
|
||||
abstract interface members to C# and the Mono runtime in a proof of concept. I did not take notes
|
||||
on the specific slides: all of that material is available publicly on the partydonk repo
|
||||
[here](https://github.com/Partydonk/partydonk/blob/master/Generic%20Math/Generic%20Math%20in%20.NET%20-%20Contractual%20Static%20Interface%20Members%20in%20CSharp.pdf).
|
||||
|
||||
Overall, the LDT members had a very positive reception to this work: in particular, they arrived
|
||||
at many of the same conclusions @CarolEidt had in her exploration of this space 10 years ago:
|
||||
much of the runtime work falls out from allowing `abstract static` in CIL and emitting constrained
|
||||
calls to such members during compilation. From a language perspective, there's still a bit of work
|
||||
to do. `override` on `abstract` static members defined in interfaces isn't particularly nice from
|
||||
a regularity with instance members perspective. We will also have to do design work around explicit
|
||||
implementations: it could all fall out from existing rules, but will need a critical eye to make
|
||||
sure that the consequences of explicitly implementing an abstract member, and what that really means.
|
||||
|
||||
The existing code uses a `TSelf` generic parameter in what could be considered kind of an unfortunate
|
||||
syntax:
|
||||
|
||||
```cs
|
||||
interface INumeric<TSelf> where TSelf : INumeric<TSelf>
|
||||
{
|
||||
abstract static TSelf Zero { get; }
|
||||
abstract static TSelf operator +(TSelf a, TSelf b);
|
||||
}
|
||||
```
|
||||
|
||||
The `TSelf : where TSelf : INumeric<TSelf>` was suggested in the past to help the prototype make
|
||||
progress without blocking on associated types, but it's ugly and proliferates through the entire
|
||||
type hierarchy if left unchecked. A much better solution would be to formally introduce associated
|
||||
types into the language, something that we've discussed in LDM before and has some support. We should
|
||||
take those into account here: if we introduce this entire type hierarchy, then in the next C# release
|
||||
introduce a `TSelf` associated type it would leave an annoying blemish on the language. In particular,
|
||||
we need to make sure we have a good roadmap for all components involved here: the compiler, the runtime,
|
||||
and the core libraries. Nonvirtual static members in interfaces have already shipped with C# 8, so we
|
||||
can't get that back and declared them virtual members of the interface.
|
||||
|
||||
We do still have a few language questions: today, you cannot create user-defined operators that involve
|
||||
interfaces, because doing so would subvert the type system. However, some numeric applications seem
|
||||
like they would want to be able to do this for constants (see `IExpressibleByIntegerLiteral` and
|
||||
`IExpressibleByFloatLiteral` in the presentation). If we allow this for the `TSelf` parameter, it seems
|
||||
like these concerns should be obviated: you're not converting to an interface type, you're converting to
|
||||
a concrete type and specifying that said conversion must be available for any implementations of the
|
||||
interface. Additionally there's an interesting correspondance issue: today, any members that are part
|
||||
of the interface contract are available on an instance of the interface. However, with static methods,
|
||||
the interface itself doesn't provide them: types that implement the interface provide them, but not the
|
||||
interface itself. We can certainly come up with new rules to cover this, but we will need to do so.
|
||||
|
||||
Some other miscellaneous topics that came up:
|
||||
* We could use this system to specify constructors with multiple parameters of a specific type. Static
|
||||
interface members could be implemented by a type calling `new`, which would allow us to improve on the
|
||||
simple `new()` that we have today.
|
||||
* Existing language built-in operators would need to be considered to satisfy the constraints of the
|
||||
numeric interfaces.
|
|
@ -1,129 +0,0 @@
|
|||
# C# Language Design Meeting for July 1st, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Non-defaultable struct types and parameterless struct constructors](#Non-defaultable-struct-types-and-parameterless-struct-constructors)
|
||||
2. [Confirming unspeakable `Clone` method implications](#Confirming-unspeakable-Clone-method-implications)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"I did not say implications, I said machinations [pronounced as in British English]. I used a big word."
|
||||
"You mispronounced machinations [pronounced as in American English], which is why I'm just ignoring you."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Non-defaultable struct types and parameterless struct constructors
|
||||
|
||||
Proposal: https://github.com/dotnet/csharplang/issues/99#issuecomment-601792573
|
||||
|
||||
#### Parameterless struct constructors
|
||||
|
||||
We discussed both proposals for allowing default struct constructors and for having a feature to allow differentiating between
|
||||
`struct` types that have a valid `default` value, and types that do not.
|
||||
|
||||
First, we looked at parameterless constructors. Today, `struct`s cannot define their own custom parameterless constructors, and
|
||||
a previous attempt to ship this feature in C# 6 failed due to a framework bug that we could not fix.
|
||||
|
||||
```cs
|
||||
public struct S
|
||||
{
|
||||
public readonly int Field = 1; // Field is set by the default constructor
|
||||
}
|
||||
|
||||
public void M<T>()
|
||||
{
|
||||
var s = (S)Activator.CreateInstance(typeof(T));
|
||||
Console.WriteLine(s.Field);
|
||||
}
|
||||
```
|
||||
|
||||
In .NET Framework and .NET Core prior to 2.0, a call to `M` will print `0`. However, .NET Core fixed this API in 2.0 to correctly
|
||||
call the parameterless constructor of a struct if one is present, and since we now tie language version to the target platform
|
||||
there will be no supported scenario with this bug.
|
||||
|
||||
While there was general support for this scenario in the LDM, we spent most of the time on the second part of the proposal and
|
||||
did not come away with a conclusion for parameterless constructors. We will need to revisit this in context of a reworked defaultable
|
||||
types proposal and make a yes/no conclusion on this feature.
|
||||
|
||||
#### Non-defaultable struct types
|
||||
|
||||
The crux of this proposal is that we would extend the tracking we introduced in C# 8 with nullable reference types, and extend it
|
||||
to value types that opt-in, holes and all. From a type theory perspective, the idea is that by applying a specific attribute, a
|
||||
struct type can indicate that `default` and `new()` are _not_ the same value in the domain of its type. In fact, if the struct does
|
||||
not provide a parameterless constructor, `new()` wouldn't be in the domain of the struct at all. This attribute would further opt the
|
||||
struct's `default` value into participating in "invalid" scenarios in the same way that `null` is part of the domain of a reference
|
||||
type, but is considered invalid for accessing members directly on that instance. This played well with our previous design of `T??`,
|
||||
if we were to allow the `??` moniker on types constrained to `struct` as well as unconstrained type parameters. However, as `??`
|
||||
has been removed from C# 9 due to syntactic ambiguities (notes [here](LDM-2020-06-17.md#T??)), that part of the proposal will have
|
||||
to be reworked. Not having `??` makes the feature much harder to explain to users, and we'll run into issues with representation in
|
||||
non-generic scenarios.
|
||||
|
||||
One thing that is clear from discussion is that non-defaultable struct types will need to have some standardized form of checking
|
||||
whether they are the default instance. `ImmutableArray<T>`, for example, has an `IsDefault` property, as do most of the existing
|
||||
struct types in Roslyn that cannot be used when `default`. We would want to be able to recognize this pattern in nullable analysis,
|
||||
just like we do today with the `is null` pattern. Since the attribute and pattern would be new, we could declare it to be whatever
|
||||
we desire, and the libraries will standardize around that if they want to participate.
|
||||
|
||||
Generics also present an interesting challenge for non-defaultable value types. Today, the `struct` constraint implies new:
|
||||
|
||||
```cs
|
||||
public void M<T>() where T : struct
|
||||
{
|
||||
var y = new T(); // Perfectly valid
|
||||
}
|
||||
```
|
||||
|
||||
If we were to enable non-defaultable struct types, this would change: `new()` is not necessarily valid on all struct types because
|
||||
non-defaultable struct types have explicitly opted to separate `default` and `new()` in their domain, and might not have provided
|
||||
a value for `new()`, meaning that it would return `default`. From an emit perspective, this is further complicated: for the above
|
||||
code, the C# compiler _already_ emits a `new()` constraint. C# code cannot actually specify both the `struct` constraint and the
|
||||
`new()` constraint at the same time today, but in order to actually emit the combination of these constraints for this feature
|
||||
we would have to introduce a new annotation on the type parameter to describe that it is required to have a parameterless `new()`
|
||||
that provides a valid instance of the type.
|
||||
|
||||
`ref` fields in structs also came up in discussion. This is a feature that we've been asked for by the runtime team and a few other
|
||||
performance-focussed areas, but is very hard to represent in C# because it would require a hard guarantee that a struct with a
|
||||
`ref` field is truly never defaulted, by anything. `ref`s do not have a "default", so a struct that contained on in a field would
|
||||
need to not be possible to default in any fashion. This proposal could overlap with that feature: the guarantees provided here are
|
||||
no stronger than the guarantees given with nullable reference types, which is to say easy to break: arrays of these structs would
|
||||
still be filled with `default` on creation, for example, even if the type wasn't annotated with a `??` or hypothetical other sigil.
|
||||
We need to be sure that, if that's the case and we do want to add `ref` fields, we're comfortable having both a "soft" and "hard"
|
||||
defaultness guarantee in the language.
|
||||
|
||||
Finally, there was some recognition and discussion around how this issue is very similar to another long standing request from
|
||||
libraries like Unity: custom nullability. The idea is that with C#, among the entire value domain of a type we recognize and have
|
||||
built language support for one _particular_ invalid value: `null`. However, this isn't the only invalid value that a value domain
|
||||
may have. Unity objects have backing C++ instances, and they override the `==` operator to allow comparison with `null` to also
|
||||
return true if the backing field has not yet been instantiated. While the C# object itself is not `null` in this case, it _is_
|
||||
invalid, and should be treated as such. However, this doesn't play well with other operators in C#, such as `?.`, `??`, and
|
||||
`is null`. These all special-case a particular invalid value, `null`, and don't play well with other invalid values, leading
|
||||
libraries like Unity to encourage users to write code that does not take advantage of modern idiomatic C# features. This issue is
|
||||
very similar to the non-defaultable structs issue: we'd like to recognize a particular value in the domain of a struct type as
|
||||
invalid. It might be better to implement this as a general invalid value pattern that any type, struct or class, can opt into.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
For both of these issues, we need to take more time and rethink them again, especially in light of the removal of `??`, which
|
||||
the non-defaultable struct type proposal relied on heavily. A small group will explore the space more, particularly the more
|
||||
general invalid object pattern, and come back with a rethought proposal. The guiding principle that this group should keep in
|
||||
mind from the current proposal is "Users should be able to change something from a class to a struct for performance without
|
||||
significant redesign due to having to handle an invalid `default` struct value."
|
||||
|
||||
### Confirming unspeakable `Clone` method implications
|
||||
|
||||
Before we ship unspeakable `Clone` methods for `with` expressions, we wanted to make sure that we've worked through the
|
||||
consequences of doing so, and are sure that the language will be able to continue to evolve without breaking scenarios that
|
||||
we are enabling with this feature. In particular, in the face of a general factory pattern that users can use to extend record
|
||||
types, or even potentially expand what is today a record type into a full blown type without breaking their customers, we
|
||||
might need to emit both the unspeakable `Clone` method and a factory method in the future. A guiding principle for record design
|
||||
has been that whether something is a record is an implementation detail. Therefore whatever future method we add that will allow
|
||||
a regular class type to participate in a `with` expression will likely have to emit this method as well.
|
||||
|
||||
We also considered whether we should take any measures right now to try and keep our design space open in records for adding a
|
||||
user-overridable `Clone` method. We could try emitting the method now, and modreq it so that it cannot be directly called from
|
||||
C# code, or we could just block users from creating a `Clone` method in a record entirely.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We're fine with the unspeakable name being a feature of records forever going forward. We will also reserve `Clone` as a member
|
||||
name in records to ensure that our future selves will be able to design in this space.
|
|
@ -1,126 +0,0 @@
|
|||
|
||||
# C# Language Design Meeting for July 6th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Repeated attributes on `partial` members](#repeated-attributes-on-partial-members)
|
||||
2. [`sealed` on `data` members](#sealed-on-data-members)
|
||||
3. [Required properties](#required-properties)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"Is there a rubber stamp icon I can use here?"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Repeated attributes on `partial` members
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/3646
|
||||
|
||||
Today, when we encounter attribute definitions among partial member definitions, we merge these attributes, applying attributes multiple
|
||||
times if there are duplicates across the definitions. However, if there are duplicated attributes that do not allow multiples, this merging
|
||||
results in an error that might not be resolvable by the user. For example, a source generator might copy over the nullable-related
|
||||
attributes from the stub side to the implementation side. This is further exacerbated by the new partial method model: previously, the
|
||||
primary generator of partial stubs was the code generator itself. WPF or other code generators would generate the partial stub, which the
|
||||
user could then implement to actually create the implementation. These generators generally wouldn't add any attributes, and the user could
|
||||
add whatever attributes they wanted. However, the model is flipped for the newer source generators. Users will put attributes on the stub in
|
||||
order to hint to the generator how to actually implement the method, so either the generator will have never copy attributes over (possibly
|
||||
complicating implementation), or it will have to be smart about only copying over attributes that matter. It would further hurt debugability
|
||||
as well; presumably tooling will want to show the actual implementation of the method when debugging, and it's likely that the tooling won't
|
||||
want to try and compute the merged set of attributes from the stub and the implementation to show in debugging.
|
||||
|
||||
What emerged from this discussion is a clear divide in how members of the LDT view the stub and the implementation of a partial member: some
|
||||
members view the stub as a hint that something like this method exists, and the implementation provides the final source of truth. This group
|
||||
would expect that, if we were designing again, all attributes would need to be copied to the implementation and attributes on the stub would
|
||||
effectively be ignored. The other segment of the LDT viewed partial methods in exactly the opposite way: the stub is the source of truth, and
|
||||
the implementation had better conform to the stub. This reflects these two worlds of previous source generators vs current generators: for the
|
||||
previous uses of partial, the user would actually be creating the implementation, so that's the source of truth. For the new uses, the user is
|
||||
creating the stubs, so that's the source of truth.
|
||||
|
||||
We also briefly considered a few ways of enabling the attribute that keys the generator to be removed from the compiled binary, so that it
|
||||
does not bloat the metadata. However, we feel that that's a broader feature that's been on the backlog for a while, source-only attributes. We
|
||||
don't see anything in this feature conflicting with source-only attributes. We also don't see anything in this feature conflicting with
|
||||
future expansions to partial members, such as partial properties.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The proposal is accepted. For the two open questions:
|
||||
|
||||
1. We will deduplicate across partial types as well as partial methods if `AllowMultiple` is false. This is considered lower priority if a
|
||||
feature needs to be cut from the C# 9 timeframe.
|
||||
2. We don't have a good use-case for enabling `AllowMultiple = true` attributes to opt into deduplication. If we encounter scenarios where
|
||||
this is needed, we can take it up again at that point.
|
||||
|
||||
### `sealed` on `data` members
|
||||
|
||||
In a [previous LDM](LDM-2020-06-22.md#data-properties), we allowed an explicit set of attributes on `data` members, but did not include
|
||||
`sealed` in that list, despite allowing `new`, `override`, `virtual`, and `abstract`. `sealed` feels like it's missing, as it's also
|
||||
pertaining to inheritance.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
`sealed` will be allowed on `data` properties.
|
||||
|
||||
### Required properties
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
In C# 9, we'll be shipping a set of improvements to nominal construction scenarios. These will allow creation of immutable objects via
|
||||
object initializers, which has some advantages over positional object creation, such as not requiring exponential constructor growth
|
||||
over object hierarchies and the ability to add a property to a base class without breaking all derived types. However, we're still
|
||||
missing one major feature that positional constructors and methods have: requiredness. In C# today, there is no way to express that a
|
||||
property must be set by a consumer, rather than by the class creator. In fact, there is no requirement that all fields of a class type
|
||||
need to be initialized during object creation: any that aren't initialized are defaulted today. The nullable feature will issue warnings
|
||||
for initialized fields inside the declaration of a class, but there is no way to indicate to the feature that this field must be initialized
|
||||
by the consumer of the class. This goes further than just the newly added `init`: mutable properties should be able to participate in this
|
||||
feature as well. In order for staged initialization to feel like a true first-class citizen in the language, we need to support requiredness
|
||||
in the contract of creating a class via the feature.
|
||||
|
||||
The LDM has seemingly universal support of making improvements here. In particular, the proposed concept of "initialization debt" resonated
|
||||
strongly with members. It allows for a general purpose mechanism that seems like it will extend natural to differing initialization modes.
|
||||
We categorized the two extreme ends of object initialization, both of which can easily be present in a single nominal record: Nothing is
|
||||
initialized in the constructor (the default constructor), and everything is initialized in the constructor (the copy constructor). The next
|
||||
question is how are these initialization contracts created: there's some tension here with the initial goal of nominal construction.
|
||||
|
||||
Fundamentally, initialization contracts can be derived in one of two ways: implicitly, or explicitly. Implicit contracts are attractive at
|
||||
first glance: they require little typing, and importantly they're not brittle to the addition of new properties on a base class, which was
|
||||
an important goal of nominal creation in the first place. However, they also have some serious downsides: In C# today, public contracts for
|
||||
consumers are always explicit. We don't have inferred field types or public-facing parameter/return types on methods/properties. This means
|
||||
that any changes to the public contract of an object are obvious when reviewing changes to that type. Implicit contracts change that. It
|
||||
would be very possible for a manually-implemented copy constructor on a derived type to miss adding a copy when a property is added to its
|
||||
base type, and suddenly all uses of `with` on that type are now broken.
|
||||
|
||||
We further observe that this shouldn't just be limited to auto-properties: a non-autoprop should be able to be marked as required, and then
|
||||
any fields that the initer or setter for that property initializes can be considered part of the "constructor body" for the purposes of
|
||||
determining if a field has been initialized. Fields should be able to be required as well. This could extend well to structs: currently,
|
||||
struct constructors are required to set all fields. If they can instead mark a field or property as required then the constructor wouldn't
|
||||
have to initialize it all.
|
||||
|
||||
One way of implementing initialization debt would be to tie it to nullable: it already warns about lack of initialization for not null
|
||||
fields when the feature is turned on. We're still in the nullable adoption phase where we have more leeway on changing warnings, so we
|
||||
could actually change the warning to warn on _all_ fields, nullable or not. This would effectively be an expansion of definite assignment:
|
||||
locals must be assigned a value before use, even if that value is the default. By extending that requirement to all fields in a class, we
|
||||
could essentially make all fields required when nullable is enabled, regardless of their type. Then, C# 10 could add a feature to enable
|
||||
skipping the initialization of some members based on whether the consumer must initialize them. This is also not really a breaking change
|
||||
for structs: they're already required to initialize all fields in the constructor. However, it would be a breaking change for classes, and
|
||||
we worry it would be a significantly painful one, especially with no ability to ship another preview before C# 9 releases. `= null!;` is
|
||||
already a significant pain point in the community, and this would only make it worse.
|
||||
|
||||
We came up with a few different ways to mark a property as being required:
|
||||
* A keyword, as in the initial proposal, on individual properties.
|
||||
* Assigning some invalid value to the field/property. This could work well as a way to be explicit about what fields a particular
|
||||
constructor would require, but does leave the issue about inherited brittleness.
|
||||
* Attributes or other syntax on constructor bodies to indicate required properties.
|
||||
|
||||
We like the idea of some indication on a property itself dictating the requiredness. This puts all important parts of a declaration together,
|
||||
enhancing readability. We think this can be combined with a defaulting mechanism: the property sets whether it is required, and then a
|
||||
constructor can have a set of levers to override individual properties. These levers could go in multiple ways: a copy constructor could
|
||||
say that it initializes _all_ members, without having to name individual members, whereas a constructor that initializes one or two specific
|
||||
members could say it only initializes those specific members, and inherits the property defaults from their declarations. There's still
|
||||
open questions in this proposal, but it's a promising first start.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We have unified consensus that in order for staged initialization to truly feel first-class in the language, we need a solution to this issue,
|
||||
but we don't have anything concrete enough yet to make real decisions. A small group will move forward with brainstorming and come back to
|
||||
LDM with a fully-fleshed-out proposal for consideration.
|
|
@ -1,213 +0,0 @@
|
|||
# C# Language Design Meeting for July 13th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Generics and generic type parameters in aliases](#Generics-and-generic-type-parameters-in-aliases)
|
||||
2. ["closed" enum types](#"closed"-enum-types)
|
||||
3. [Allow `ref` assignment for switch expressions](#Allow-`ref`-assignment-for-switch-expressions)
|
||||
4. [Null suppressions nested under propagation operators](#Null-suppressions-nested-under-propagation-operators)
|
||||
5. [Relax rules for trailing whitespaces in format specifier](#Relax-rules-for-trailing-whitespaces-in-format-specifier)
|
||||
6. [Private field consideration in structs during definite assignment analysis](#Private-field-consideration-in-structs-during-definite-assignment-analysis)
|
||||
7. [List patterns](#list-patterns)
|
||||
8. [Property-scoped fields and the `field` keyword](#Property-scoped-fields-and-the-field-keyword)
|
||||
9. [File-scoped namespaces](#file-scoped-namespaces)
|
||||
10. [Allowing `ref`/`out` on lambda parameters without explicit type](#Allowing-ref/out-on-lambda-parameters-without-explicit-type)
|
||||
11. [Using declarations with a discard](#Using-declarations-with-a-discard)
|
||||
12. [Allow null-conditional operator on the left hand side of an assignment](#Allow-null-conditional-operator-on-the-left-hand-side-of-an-assignment)
|
||||
12. [Top level statements and functions](#Top-level-statements-and-functions)
|
||||
13. [Implicit usings](#implicit-usings)
|
||||
|
||||
## Quote of the day
|
||||
|
||||
"It does actually seem like this pattern is the 98% case..."
|
||||
"I just want to disagree with something [redacted] said... [they] said [they] thought it was the 98% case that this would apply to, and I think it's 99..."
|
||||
"Not fair, I was going to say that."
|
||||
|
||||
## Discussion
|
||||
|
||||
This meeting was issue triage. There will not be much detail on any particular issue, just a general summary
|
||||
of the LDM's feeling and the milestone that we triaged to.
|
||||
|
||||
### Generics and generic type parameters in aliases
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1239
|
||||
|
||||
In general, we want to make improvements in this area. We should also be open to making other enhancements
|
||||
in this space, such as allowing tuple syntax on the right side, or allowing C# keywords like `int`. There's
|
||||
also a set of features that we should investigate in parallel, such as globally-visible aliases or publicly-
|
||||
exported aliases.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for C# 10 for now so that we can look at the space hollistically in the near term.
|
||||
|
||||
### "closed" enum types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3179
|
||||
|
||||
This is a solution to the very common complaints around enum usage, particularly in switch expressions and other
|
||||
exhaustiveness scenarios. It will also work well with DUs, and we should make sure that whatever syntax we use
|
||||
there works well with closed or sealed enum types as well.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for C# 10 to look at in coordination with DUs.
|
||||
|
||||
### Allow `ref` assignment for switch expressions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3326
|
||||
|
||||
We like this proposal. You can already do this with ternaries, and it's odd that you can't do this with switch
|
||||
expressions as well. It's not high priority, but we'd be happy to see it in the language. Like ternaries, the
|
||||
ref version would not be target-typed.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for Any Time.
|
||||
|
||||
### Null suppressions nested under propagation operators
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3393
|
||||
|
||||
This will be a breaking change, so we need to get it in as soon as possible.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
C# 9 timeframe. We need to make a syntax proposal and approve it.
|
||||
|
||||
### Relax rules for trailing whitespaces in format specifier
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3414
|
||||
|
||||
In reading this proposal, we're unsure whether the request was for _ignoring_ trailing spaces in format specifiers,
|
||||
or allowing them and including them in the format specifier. We think that either way this is interpreted, it will
|
||||
be confusing: trailing spaces are allowed in front of an interpolated expression and mean nothing, but for things
|
||||
like `DateTime`, spaces are valid parts of a format specifier and will be respected in the output. We think that
|
||||
either behavior here would be confusing, with no clear indication on which the user wants.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Rejected.
|
||||
|
||||
### Private field consideration in structs during definite assignment analysis
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3431
|
||||
|
||||
This is a bug from the native compiler that had to be reimplemented in Roslyn for compat, but it's always been
|
||||
one of the prime candidates for a warning wave (and is already supported as an error in the compiler via the strict
|
||||
feature flag). There is some contention whether it should be a warning or an error in this wave. There is also a
|
||||
concern that this will combine with the `SkipLocalsInit` feature in C# 9 to expose an uninitialized memory hole in
|
||||
C# with no use of `unsafe` or `System.Unsafe` required.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We need to look at this ASAP to make sure that we don't unintentionally expose unsafety in the language without
|
||||
user intention. We'll schedule this for next week.
|
||||
|
||||
### Remove restriction that optional parameters must be trailing
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3434
|
||||
|
||||
It's possible to define such methods in metadata with the correct attribute usage _and_ call them from C# today.
|
||||
This would just be about removing the restriction from _defining_ them in C#.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for Any Time.
|
||||
|
||||
### List patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3435
|
||||
|
||||
We have an open proposal that with a prototype. We like the general direction it takes, but we need to have more
|
||||
detailed design review of it and possibly make a few different decisions. This is a direction we want to take
|
||||
pattern matching in future releases, so we'll continue iterating on this.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged into C# 10 for design work.
|
||||
|
||||
### Property-scoped fields and the `field` keyword
|
||||
|
||||
* https://github.com/dotnet/csharplang/issues/133
|
||||
* https://github.com/dotnet/csharplang/issues/140
|
||||
|
||||
We've started doing design work around both of these issues. We should continue doing that.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged into C# 10.
|
||||
|
||||
### File-scoped namespaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/137
|
||||
|
||||
This has been a large request for a long time that reflects the default of almost every single C# file written.
|
||||
There's still some design work to do, particularly in how that will affect `using` statements, but it's work
|
||||
that we should take on.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged into C# 10.
|
||||
|
||||
### Allowing `ref`/`out` on lambda parameters without explicit type
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/338
|
||||
|
||||
We've heard this request a few times, and while we don't see any particular issue with this, it's not a priority
|
||||
for the team at this point. If a spec/implementation was provided we'd likely accept it but won't go out of our
|
||||
way to add it unless something else changes here.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged to Any Time.
|
||||
|
||||
### Using declarations with a discard
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2235
|
||||
|
||||
There's still some open design questions here, particularly around possibly ambiguous syntaxes. We need to keep
|
||||
iterating on this to find a design that we like.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged for X.X, pending a new proposal.
|
||||
|
||||
### Allow null-conditional operator on the left hand side of an assignment
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2883
|
||||
|
||||
Initial discussion shows a big LDT split on whether `?.` should be able to effectively a conditional ref. We
|
||||
would need to discuss further, but aren't ready to outright approve or reject the feature.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triage to X.X for future discussion.
|
||||
|
||||
### Top level statements and functions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3117
|
||||
|
||||
This is part 2 of the work we started in C# 9 with top-level statements: designing free-floating functions that
|
||||
can sit on the top level without any containing type. We need to continue examining this context of scripting
|
||||
unification, which we plan to continue doing on an ongoing basis.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triage into C# 10 for ongoing discussion.
|
||||
|
||||
### Implicit usings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3428
|
||||
|
||||
This is another issue arising of discussions on top-level statements and using C# as a scripting language:
|
||||
attempting to make the language more brief by specifying common imports on the command line or as part of
|
||||
the project file is certainly one way to remove boilerplate. However, it's a controversial feature that often
|
||||
draws visceral reactions. We need to discuss this more and figure out if it's a feature we want to have in C#,
|
||||
taking into account our increased focus on scripting-like scenarios. It's worth noting that CSX has a limited
|
||||
form of this support already, and implementing this in C# proper would bring us one step closer to unifying the
|
||||
two dialects.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triage to C# 10 for discussion.
|
|
@ -1,113 +0,0 @@
|
|||
# C# Language Design Meeting for July 20th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Finishing Triage](#finishing-triage)
|
||||
a. [Extend with expression to anonymous type](#Extend-with-expression-to-anonymous-type)
|
||||
b. [Required properties](#Required-properties)
|
||||
c. [Shebang support](#shebang-support)
|
||||
2. [Private reference fields in structs with `SkipLocalsInit`](#Private-reference-fields-in-structs-with-`SkipLocalsInit`)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"If you go get a beer from the fridge or wherever you keep them and drink it right now, I'll refund you."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Finishing Triage
|
||||
|
||||
#### Extend with expression to anonymous type
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3530
|
||||
|
||||
We don't see any reason why we couldn't extend `with` expressions to handle anonymous types, and we universally support
|
||||
the idea. We do see room for further improvement as well. Struct types should be generally possible to `with`, and in
|
||||
particular tuples should feel first class here. Some other ideas for future improvements that we'll need to keep in mind
|
||||
when designing anonymous types is an ability to specify a `withable` constraint: perhaps that's a thing we could do via
|
||||
typeclasses, but if that's a thing that should be specifiable then we'll want to make sure that whatever we do for structs
|
||||
and anonymous types works well with it.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into C# 10 for discussion with the other `with` enhancements
|
||||
|
||||
#### Required properties
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
A smaller group is currently fleshing this proposal out in the direction of initializer debt, and is looking to talk more
|
||||
about it in the next few months.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged for C# 10.
|
||||
|
||||
#### Shebang support
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3507
|
||||
|
||||
This is part of the next step in the C# scripting discussion. In particular, this proposal details just one, very small
|
||||
piece of the puzzle here, namely the ability have a C# file startup an environment. There is significantly more work to
|
||||
be done in the language and tooling to effectively modernize the loose-files support for C#. Today, C# code has a core
|
||||
concept that cs files themselves do not contain information about the runtime environment. They don't specify the target
|
||||
runtime, nuget dependencies, where tools can be found, or anything else similar (beyond some `#ifdef` blocks). Supporting
|
||||
this would be intentionally eroding this aspect, which we need to make sure we're doing with intention and design. There
|
||||
is support among LDT members for this scenario, so we'll continue to look at.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into C# 10 for discussion. We acknowledge that we may well not ship anything in this space as part of C# 10, but
|
||||
we want to start discussing possible futures here in the coming X months, not X years.
|
||||
|
||||
### Private fields in structs with `SkipLocalsInit`
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3431
|
||||
|
||||
Proposal 1: https://github.com/dotnet/roslyn/issues/30194#issuecomment-657858716
|
||||
Proposal 2: https://github.com/dotnet/roslyn/issues/30194#issuecomment-657900257
|
||||
|
||||
This topic was brought up last week when we realized there was a potential hole in `SkipLocalsInit` with definite
|
||||
assignment, where we realized it's possible to observe uninitialized memory via private fields. When importing metadata,
|
||||
the native compiler ignored private fields, including private fields in structs, and did not require them to be considered
|
||||
definitely assigned before usage. This behavior was preserved when we implemented Roslyn, as attempting to break it was
|
||||
too large of a change for organizations to adopt. Both of these proposals ensure that, with `SkipLocalsInit` on a method,
|
||||
it's considered an error to not definitely assign here, which was fine with LDM as this is a new feature and cannot break
|
||||
anyone. We then looked at the differences between the two proposals, namely whether the definite assignment diagnostic
|
||||
should be a warning or an error when users opt into to a new warning wave. We found the arguments around incremental
|
||||
adoption compelling: we don't want users to be blocked off from adopting new warning waves and making their code at least
|
||||
a little safer by issuing errors that cannot be ignored. If users want to ensure that these warnings are fixed in their
|
||||
code, they can use `warnAsError` to turn these specific warnings or all warnings into errors, as you can today.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Proposal 1 has been chosen:
|
||||
|
||||
1. If a method has the [SkipLocalsInit] attribute, or the compilation has the "strict" feature enabled, we use the strong
|
||||
version of analysis for its body. Otherwise
|
||||
2. If a sufficiently high /warnversion (>= 5) is enabled, we run the strong version of analysis, and
|
||||
a. If it reports no diagnostics, we are done.
|
||||
b. If it reports diagnostics, we run the weak version of analysis. Errors that are common to the two runs are reported
|
||||
as errors, and errors only reported by the strong version are downgraded to a warning.
|
||||
3. Otherwise we run the weak version of analysis.
|
||||
|
||||
### Future record direction
|
||||
|
||||
See https://github.com/dotnet/csharplang/issues/3707 for the full list. We briefly went through the list here to gauge
|
||||
support from LDT members on the various proposals. We didn't get particularly in depth on any one part, but some
|
||||
highlights:
|
||||
|
||||
* `init` fields and methods - We rejected these in 9, deciding to wait for community feedback. We believe this is still
|
||||
the right decision, and want to hear user scenarios before proceeding further.
|
||||
* Final initializers - We briefly entertained the idea of having an `init { }` syntax to guarantee that a block is called
|
||||
after a method. This was later rejected as too complicated after design review. Like `init`, we need to get a better
|
||||
handle on the user scenarios.
|
||||
* Required members - We have broad support for doing something in this space.
|
||||
* Factores - We have broad support for doing something in this space.
|
||||
* User-defined cloning - Will likely need to be designed hand-in-hand with factories
|
||||
* Cross-inheritance between records and non-records - part of the initial record goal was that `record` is pure syntactic
|
||||
sugar. If that's still a goal, then we need to do more work here.
|
||||
* Generalized primary constructors - Mixed support here. We need to do more design work.
|
||||
* Primary constructor bodies - Might be done for the former. We need to flesh this out so we can determine how to apply
|
||||
attributes to record constructors.
|
||||
* Discriminated unions - We need to determine whether this will be a simple syntactic shorthand for a general sealed type
|
||||
hierarchy feature, or if this will be the only way to declare such hierarchies.
|
|
@ -1,186 +0,0 @@
|
|||
# C# Language Design Meeting for July 27th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
- [Improved nullable analysis in constructors](#Improved-nullable-analysis-in-constructors)
|
||||
- [Equality operators in records](#Equality-operators-in-records)
|
||||
- [`.ToString()` or `GetDebuggerDisplay()` on records](#ToString-or-DebuggerDisplay-on-record-types)
|
||||
- [W-warnings for `T t = default;`](#W-warnings-for-`T-t-=-default;`)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"We should pick our poison: one is arsenic, and will kill us. The other is a sweet champagne, and will give us a headache tomorrow."
|
||||
|
||||
![Delectable Tea or Deadly Poison](delectable_tea_2020_07_27.png)
|
||||
|
||||
## Procedural Note
|
||||
|
||||
LDM is going on August hiatus as various members will be out. We'll meet next on August 24th, 2020.
|
||||
|
||||
## Discussion
|
||||
|
||||
### Improved nullable analysis in constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/nullable-constructor-analysis.md
|
||||
|
||||
This proposal is a tweak to the nullable warnings we emit today in constructors that would bring these warnings more in line with
|
||||
behavior for `MemberNotNull`, as well as addressing a few surprising cases where you do not get a deserved nullable warning today.
|
||||
This code, for example, has no warnings today:
|
||||
```cs
|
||||
public class C
|
||||
{
|
||||
public string Prop { get; set; }
|
||||
public C() // we get no "uninitialized member" warning, as expected
|
||||
{
|
||||
Prop.ToString(); // unexpectedly, there is no "possible null receiver" warning here
|
||||
Prop = "";
|
||||
}
|
||||
}
|
||||
```
|
||||
We consider this to be possible to retrofit into the nullable feature, as we're still within the nullable adoption phase. After
|
||||
C# 9 is released, however, this would be a breaking change, so we will need to either do it now, or do it in a warning wave
|
||||
(which would heavily complicate the implementation). Additionally, even though we're still within the nullable adoption phase,
|
||||
this is a relatively big change in warnings that could result in lots of new errors in codebases that have heavily adopted nullable.
|
||||
We also considered whether there could be any interference here with C# 10 plans around required properties or initialization debt.
|
||||
There does not appear to be, as this would effectively be the initialization debt world where a constructor is required to set all
|
||||
properties in the body, and any relaxation of this by requiring properties to be initialized at construction site will play well.
|
||||
|
||||
#### Conclusion:
|
||||
|
||||
We should implement the change and smoke test it on roslyn, the runtime repo, and on VS. If there is large churn, we'll re-evaluate
|
||||
at that point.
|
||||
|
||||
### Equality operators in records
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3707#issuecomment-661800278
|
||||
|
||||
We've talked briefly about equality operators in records prior to now, but we never ended up making any firm decisions, so now we
|
||||
need to make an intentional decision about records before we ship as doing so after would be a breaking change. The concern is that,
|
||||
because class types inherit a default equality operator from simply being a class, we'll get into a scenario where a record's `Equals`
|
||||
method could return `true`, and the `==` operator returns false. Most standard .NET "values" behave in this fashion as well: `string`
|
||||
being the most common example.
|
||||
|
||||
There are some interesting niggles, however, particularly with `float` and `double` values. For example,
|
||||
this code:
|
||||
```cs
|
||||
var a = (float.NaN, double.NaN);
|
||||
System.Console.WriteLine(a == a); // Prints false
|
||||
System.Console.WriteLine(a.Equals(a)); // Prints true
|
||||
```
|
||||
This happens because the `==` operator and the `Equals` method for floats and doubles behave differently. The `==` operator uses IEEE
|
||||
equality, which does not consider `NaN` equal to itself, while `Equals` uses the CLR definition of equality, which does. When combined
|
||||
with `ValueTuple`, this makes for an interesting set of behaviors, as `ValueTuple` doesn't have its own definition of the `==` operator.
|
||||
Rather, the compiler synthesizes the set of `==` operators on the component elements of the tuple. This means that for generic cases,
|
||||
such as `public void M<T>((T, T) tuple) { _ = tuple == tuple; }`, you get an error when attempting to use `==` on the tuple, since `==`
|
||||
is not defined on generic type parameters. `ValueTuple` is a particularly special case here though. The compiler special cases knowledge
|
||||
of the implementation, which is not generalizable across inheritance hierarchies with record types. In order for an implementation to do
|
||||
memberwise `==` across all levels of a record type, there will have to be hidden virtual methods to take care of this, and it gets more
|
||||
complicated when you consider that a derived record type could introduce a generic type parameter field that can't be `==`'d. We feel
|
||||
that attempting to get too clever here would end up being unexplainable, so if we want to implement the operators, it should just
|
||||
delegate to the virtual `Equals` methods we already set up.
|
||||
|
||||
Next, we considered whether we should implement `==` operators on all levels of a record inheritance hierarchy, or just the top level.
|
||||
With records as they're shipping in C# 9, we don't believe that there's a scenario you can get into that would break this. However, the
|
||||
eventual goal is to enable records and classes to inherit from each other, and that point if every level didn't provide its own
|
||||
implementation it could end up being possible to break assumptions. Further, attempting to trim implementations of `==` and `!=` would
|
||||
end up resulting in complicated rules that don't feel necessary: adding the operators isn't going to bloat metadata size, will potentially
|
||||
allow eliminating of some levels of equality method calls, and future proofs ourselves.
|
||||
|
||||
Finally, we looked at whether the user will be able to provide their own implementation of `==` and `!=` operators, and if we'd error
|
||||
on this or allow it and not generate the operators ourselves in this case. We feel that allowing this would complicate records: there
|
||||
are existing extension points into record equality that can be overridden as needed, and we want to have a stronger concept of `Equals`
|
||||
and `==` being the same. If there are good user scenarios around allowing this, we can consider it at a later point.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will generate the `==` and `!=` operators on every level of a record hierarchy. These implementations will not be user-overridable,
|
||||
and they will delegate to `.Equals`. The implementation will have this semantic meaning:
|
||||
```cs
|
||||
pubic static bool operator==(R? one, R? other)
|
||||
=> (object)one == other || (one?.Equals(other) ?? false);
|
||||
public static bool operator!=(R? one, R? other)
|
||||
=> !(one == other);
|
||||
```
|
||||
|
||||
### `ToString` or `DebuggerDisplay` on record types
|
||||
|
||||
#### Initial proposal
|
||||
|
||||
1. `record` generates a ToString that prints out its type name followed by a positional syntax: Point(X: 1, Y: 2).
|
||||
(issue: disfavors nominal records)
|
||||
2. `record` generates a ToString that does the above and also appends nominal properties by calling ToStringNominal:
|
||||
`FirstName: First, LastName: Last`, and assembles the parts into `Person { FirstName: First, LastName: Last, ID: 42 }`.
|
||||
|
||||
#### Discussion
|
||||
|
||||
The initial proposal here is to add a `ToString` method that would format a record type by their positional properties, and have a
|
||||
separate method for creating a string from nominal properties named something like `ToStringNominal`. The initial reaction was that
|
||||
this seemed overly complicated: there should just be one method that does "the right thing<sup>tm</sup>". There's further question
|
||||
about whether having a `ToString` or `DebuggerDisplay` would be useful: depending on the properties of the record type, it could end
|
||||
up doing more harm than good (if the properties were lazy, for example). Additionally, when a `ToString` is provided it's often domain
|
||||
specific, and nothing we do in the compiler would be able to predict what shape that should be. We ended up coming up with a list of
|
||||
common scenarios in which a `ToString` would be useful:
|
||||
|
||||
* Viewing in the debugger. This could be done with just a `DebuggerDisplay` attribute, or users can just expand the object to view the
|
||||
properties of it as you can today.
|
||||
* Viewing in test output. Using an equality test, for example, will usually print the objects if they were not equal to each other,
|
||||
and it would be useful if that was actually meaningful, particularly for these value-like class types.
|
||||
* Logging output. This is a very similar case to the previous.
|
||||
|
||||
We also considered whether we should implement this via source generators. However, it feels very similar to equality: we have a
|
||||
sensible default there, and anyone who wants to customize (this field should be excluded, this one should use sequence equality)
|
||||
can either write it manually or use a source generator. The same arguments apply here: we can provide a sensible default that will
|
||||
enable short syntax, and anyone who wants to get custom behavior can override this.
|
||||
|
||||
After considering _whether_ to do this feature, we looked at what properties would be included in such a feature. How, would they
|
||||
be formatted, and which ones are considered? The initial proposal was for positional only, with a separate method for nominal
|
||||
properties. We felt that this was too complicated and likely not to be the right defaults: it totally disadvantages nominal records
|
||||
and makes the implementation more complicated. The options we considered are:
|
||||
|
||||
* The same members as .Equals. This means all fields of a class, translating auto-generated backing fields to their properties for
|
||||
display names. This would make explaining what appears in the `ToString` simple: it's the equality members, full stop.
|
||||
* The positional members and `data` members of a record. We feel that this is too restrictive, especially since `data` members will
|
||||
not be shipping in final form with C# 9: they'll still be under the preview language version.
|
||||
* The fields and properties of a record type with some accessibility, accessibility to be determined next. We believe that it's very
|
||||
unusual for a `ToString` to display `private` fields, which the other proposal would do.
|
||||
|
||||
We leaned towards just doing the fields and properties of a type, with some accessibility. Finally, we looked at what accessibility
|
||||
to use by default for these:
|
||||
|
||||
* All members not marked `private`. This means that things that not relevant to a consumer of the type, such as `protected` fields,
|
||||
show up in the `ToString`, and violates encapsulation principles.
|
||||
* All `public` and `internal` members. This propsal has some of the same issues as the previous one: you could making an `internal`
|
||||
type that implements a `public` interface, and it would be odd if the final user sees that internal state.
|
||||
* At least as accessible as the record itself. This would mean that if the `record` was `internal`, then internal members would be
|
||||
shown. If the record was `public`, then only `public` members would be shown. This doesn't solve any of our issues with the previous
|
||||
proposal, and adds a behavioral difference between making a type `public` or `internal`.
|
||||
* Only `public` members. This is what the positional constructor will generate by default, and it feels like the most natural state
|
||||
to choose. This ensures that encapsulation isn't violated, and records with lots of `internal` or `private` state that want to
|
||||
display these in a `ToString` are likely not really records anyway. If the user really wants to change this, they can customize it
|
||||
as they choose.
|
||||
|
||||
Conclusion:
|
||||
|
||||
We'll have a generated `ToString`, that will display the public properties and fields of a `record` type. We'll come back with a
|
||||
specific proposal on how this will be implemented at a later point.
|
||||
|
||||
### W-warnings for `T t = default;`
|
||||
|
||||
Just after we initially shipped C# 8, we made a change to warn whenever `default` was assigned to a local or property of type `T`,
|
||||
or when a default was cast to that type. There was no way to silence this warning without using a `!`, so we reverted the change.
|
||||
However, now that we are shipping `T?`, there is an actual syntax that users can write in order to express "this location might
|
||||
be assigned default". We know from experience that this is something that users already do a lot, so making a change here would be
|
||||
pretty breaking, even for the nullable rollout phase of the first year. Further, as we know that if the `!` is the only way to get
|
||||
rid of this warning then we'll get lots of feedback from users, we believe that we can't unconditionally warn here: you can only
|
||||
get rid of the warning in C# 9, so at a minimum it should only be reported in C# 9. We could also put this in a warning wave, and
|
||||
you would only get it when you opt into the warning wave. An important thing to consider with doing this as a warning wave implies
|
||||
that it should be separate error code from 8600: we've previously told people that if they don't want to annotate all their local
|
||||
variables with `?`s, then they should disable that warning and all the rest of the warnings will be actual safety warnings. This
|
||||
would add another warning to that list to disable. We could make it memorable as well, but one of the key aspects in warning waves
|
||||
is that you should be able to opt out of individual warnings if necessary, so users will need to be able to opt out of this new
|
||||
warning without turning off all the existing 8600 warnings. All that being said, there's also a serious backcompat concern here,
|
||||
as introducing the warning when upgrading to C# 9 under the existing error code could cause people to hold off on upgrading at all.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll add a new warning here, under a warning wave.
|
|
@ -1,196 +0,0 @@
|
|||
# C# Language Design Meeting for July 27th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
- [Warnings on types named `record`](#warnings-on-types-named-record)
|
||||
- [`base` calls on parameterless `record`s](#base-calls-on-parameterless-records)
|
||||
- [Omitting unnecessary synthesized `record` members](#omitting-unnecessary-synthesized-record-members)
|
||||
- [`record` `ToString` behavior review](#record-tostring-behavior-review)
|
||||
- [Behavior of trailing commas](#behavior-of-trailing-commas)
|
||||
- [Handling stack overflows](#handling-stack-overflows)
|
||||
- [Should we omit the implementation of `ToString` on `abstract` records](#should-we-omit-the-implementation-of-tostring-on-abstract-records)
|
||||
- [Should we call `ToString` prior to `StringBuilder.Append` on value types](#should-we-call-tostring-prior-to-stringbuilder.append-on-value-types)
|
||||
- [Should we try and avoid the double-space in an empty record](#should-we-try-and-avoid-the-double-space-in-an-empty-record)
|
||||
- [Should we try and make the typename header print more economic](#should-we-try-and-make-the-typename-header-print-more-economic)
|
||||
- [Reference equality short circuiting](#reference-equality-short-circuiting)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
"He was hung up on his principles!"
|
||||
|
||||
"I painted my tower all ivory."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Warnings on types named `record`
|
||||
|
||||
We previously discussed warning in C# 9 when a type is named `record`, as it will need to be escaped in field
|
||||
definitions in order to parse correctly. This is a breaking change being made in C# 9, but we didn't explicitly
|
||||
record the outcome of the warning discussion. We also discussed going further here: we could make lower-cased
|
||||
type names a warning with the next warning wave, under the assumption that lowercase names make good keywords.
|
||||
For example, we could start warning on types named `async` or `var`. Those who want to forbid usage of those
|
||||
features in their codebase have a solution in analyzers.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will warn on types named `record` in C# 9. Further discussion will need to happen for lowercase type names
|
||||
in general, as there are globalization considerations, and we feel that disallowing `record` might be a good
|
||||
smoke test for reception to the idea in general.
|
||||
|
||||
### `base` calls on parameterless `record`s
|
||||
|
||||
We have a small hole in records today where this is not legal, and there is no workaround to making it legal:
|
||||
|
||||
```cs
|
||||
record Base(int a);
|
||||
record Derived : Base(1);
|
||||
```
|
||||
|
||||
The spec as written today explicitly forbids this: "It is an error for a record to provide a `record_base`
|
||||
`argument_list` if the `record_declaration` does not contain a `parameter_list`." However, there is good
|
||||
reason to allow this, and while this design may have fallen out of the principles that were set out given
|
||||
that a default constructor for a nominal record is not recognized as a primary constructor, this seems to
|
||||
violate principles around generally making record types smaller. Further, with the spec as written today
|
||||
it's not possible to work around this issue by giving `Derived` an empty parameter list, as parameter lists
|
||||
are not permitted to be empty.
|
||||
|
||||
A solution to this problem is relatively straightforward: we make the default constructor of a nominal record
|
||||
the primary constructor, if no other constructor was provided. This seems to mesh well with the existing rules,
|
||||
and seems to work well with future plans for generalized primary constructors. We could consider 2 variants of
|
||||
this: requiring empty parens on the type declaration in all cases, or only requiring them in cases where the
|
||||
default constructor would otherwise be omitted (if another constructor was added in the `record` definition).
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll allow empty parens as a constructor in C# 9. We're also interested in allowing the parens to be omitted
|
||||
when a default constructor would otherwise be emitted, but if this doesn't make it in C# 9 due to time
|
||||
constraints it should be fine to push back to v next.
|
||||
|
||||
### Omitting unnecessary synthesized `record` members
|
||||
|
||||
Some record members might be able to be omitted in certain scenarios if they're unneeded. For example, a
|
||||
`sealed` `record` that derives from `System.Object` would not need an `EqualityContract` property, as it
|
||||
can only have the one contract. We could also simplify the implementation of `ToString` if we know there
|
||||
will be no child-types that need to call `PrintMembers`.
|
||||
|
||||
However, despite leading to simplified emitted code for these types, we're concerned that this will cause
|
||||
other code to become more complicated. We'll need to have more complicated logic in the compiler to handle
|
||||
these cases. Further, source generators and other tools that interact with records programmatically will
|
||||
have to duplicate this logic if they need to interact with record equality or any other portion of code that
|
||||
"simplified" by this mechanism. We further don't see a real concern for metadata bloat in this scenario,
|
||||
as we aren't omitted fields from the record declaration.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
There are likely more downsides than upsides to doing something different here. We'll continue to have this
|
||||
be regular.
|
||||
|
||||
### `record` `ToString` behavior review
|
||||
|
||||
Now that we have a proposal and implementation for `ToString` on record types, we'll go over some standing
|
||||
questions from initial implementation review. Prior art for this area in C# that we found relevant was
|
||||
`ToString` on both tuples and anonymous types.
|
||||
|
||||
#### Behavior of trailing commas
|
||||
|
||||
There is a proposal to simplify the implementation of `ToString` by always printing trailing commas after
|
||||
properties. This would remove the need for some bool tracking flags for whether a comma was printed as
|
||||
part of a parent's `ToString` call. However, no prior art (in either C# or other languages) that we could
|
||||
find does this. Further, it provokes a visceral reaction among team members as feeling unfinished, and that
|
||||
it would be the first bug report we get after shipping it.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
No trailing commas.
|
||||
|
||||
#### Handling stack overflows
|
||||
|
||||
There is some concern that `ToString` could overflow if a record type is recursive. While this is also true
|
||||
of equality and hashcode methods on records, there is some concern that `ToString` might be a bit special
|
||||
here, as it's the tool that a developer would use to get a print-out of the record to find the cyclic element
|
||||
in the first place. There's examples in the javascript world of this being a pain point. However, any
|
||||
solution that we come up with for records will have limitations: it wouldn't be able to protect against
|
||||
indirect cycles through properties that are records but not records of the same type, or against cycles
|
||||
in properties that are not records at all. In general, if a user makes a record type with a cyclic data
|
||||
structure, this is a problem they're going to have to confront already in equality and hashcode.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We will not attempt any special handling here. We could consider adding a `DebuggerDisplay` with more special,
|
||||
reflective handling at a later point in time if this proves to be a pain point, but we don't think it's
|
||||
worth the cost in regular `ToString` calls.
|
||||
|
||||
#### Quoting/escaping values
|
||||
|
||||
Today, records do not attempt to escape `string`s when printing, which could result in potentially confusing
|
||||
printing. However, we do have concerns here. First, none of our prior art in C# does this escaping. Second,
|
||||
the framework also does not escape `ToString()` like this. Third, there's a question of what _type_ of
|
||||
escaping we should do here. Would we want to print a newlines as the C# version, `\n`, or the VB version?
|
||||
Further, we already have some handling the expression evaluator and debugger for regular strings and
|
||||
anonymous types, to ensure that the display looks good there. Given that this is mainly about ensuring
|
||||
that records appear well-formatted in the debugger, it seems like the correct and language-agnostic approach
|
||||
is to ensure the expression evaluator knows about record types as well.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We won't do any quoting or escaping of string record members. We should make the debugger better here.
|
||||
|
||||
#### Should we omit the implementation of `ToString` on `abstract` records
|
||||
|
||||
We could theoretically omit providing an implementation of this method on `abstract` record types as it
|
||||
will never be called by the derived `ToString` implementation. On the face of it, we cannot think of a
|
||||
scenario that would be particularly helped by including the `ToString`, however we also cannot think of
|
||||
a scenario that would be harmed by including it, and doing so would make the compiler implementation simpler.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Keep it.
|
||||
|
||||
#### Should we call `ToString` prior to `StringBuilder.Append` on value types
|
||||
|
||||
The concern with directly calling `Append` without `ToString` on the value being passed is that for
|
||||
non-primitive struct types, this will cause the struct to be boxed, and the behavior of `Append` is
|
||||
to immediately call `ToString` and pass to the `string` overload.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Do it.
|
||||
|
||||
#### Should we try and avoid the double-space in an empty record
|
||||
|
||||
The implementation currently prints `Person { }` for an empty record. This provokes immediate "unfinished"
|
||||
reactions in the LDT, similar to the trailing commas above.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Should we remove it? YES! YES! YES! YES!
|
||||
|
||||
#### Should we try and make the typename header print more economic
|
||||
|
||||
Today, we call `StringBuilder.Append(nameof(type))` and `StringBuilder.Append("{")` immediately after
|
||||
as 2 separate calls, which is another method call we could drop if we did it all in one. We feel that this
|
||||
is sufficiently outside the language spec as to be a thing a compiler could do if it feels it appropriate.
|
||||
From the compiler perspective, however, it could actually be bad change, as it would increase the size of
|
||||
the string table, which is known to be an issue in some programs, while getting rid of one extra method call
|
||||
in a method not particularly designed to be as fast as possible in the first place.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Up to implementors. However, we don't believe it to be a good idea or worth any effort.
|
||||
|
||||
### Reference equality short circuiting
|
||||
|
||||
Today, we have a slight difference between the equality operators `==`/`!=` and the implementation of the
|
||||
`Equals(record)` instance method, in that the former compare reference equality first, before delegating
|
||||
to the `Equals` method. This ensures that `null` is equal to `null` as a very quick check, and ensures we
|
||||
only have to compare one operand to `null` explicitly in the implementation of the operators. The `Equals`
|
||||
instance member then does not check this condition. However, this means that the performance characteristics
|
||||
of these two members can be very different, with the operators being an order of magnitude faster on even
|
||||
small record types since it has to do potentially many more comparisons. The proposal, therefore, is to
|
||||
check reference equality in the `Equals` instance member as well, as the first element.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will do this. We'll keep the reference equality check in the operators as well to ensure that `null == null`,
|
||||
and add one at the start of the instance method. It does mean that the `==` operators will check reference
|
||||
equality twice, but this is an exceptionally quick check so we don't believe it's a big concern.
|
|
@ -1,190 +0,0 @@
|
|||
# C# Language Design Meeting for September 9th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Triage issues still in C# 9.0 candidate](#triage-issues-still-in-C#-9.0-candidate)
|
||||
2. [Triage issues in C# 10.0 candidate](#Triage-issues-in-C#-10.0-candidate)
|
||||
|
||||
# Quote(s) of the Day
|
||||
|
||||
"Don't even mention I was here."
|
||||
|
||||
"I have lots of strong opinions, I'm just judicious with how I distribute them."
|
||||
|
||||
# Discussion
|
||||
|
||||
## Triage issues still in C# 9.0 candidate
|
||||
|
||||
To start, we updated https://github.com/dotnet/csharplang/blob/master/Language-Version-History.md with the features that have been merged into the compiler for C# 9 at
|
||||
this point, and moved their corresponding issues to the [9.0 milestone](https://github.com/dotnet/csharplang/milestone/18).
|
||||
|
||||
### Null propagation expression does not allow `!` as a nested operator form
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3393
|
||||
|
||||
We did not adequately prioritize this given the urgency of the breaking change. We need to attempt to get this into C# 9 or we may end up being stuck with this behavior
|
||||
given that the nullable rollout period is ending, or we'd need to consider taking what could be a large breaking change. We'll attempt to throw and catch this hail mary,
|
||||
and it will stay in C# 9.0 candidate for now.
|
||||
|
||||
### [Proposal] Improve overload resolution for delegate compatibility
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3277
|
||||
|
||||
We had hoped to get this done as a refactoring while implementing function pointers, but did not end up getting it in. This is a good candidate for community contribution
|
||||
as it can be implemented and tested in a single PR, so we'll move this to any time.
|
||||
|
||||
### Interface static method, properties and event should ignore variance
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3275
|
||||
|
||||
This is implemented, just waiting on infrastructure to move forward to a runtime where it can be tested. Will be in C# 9 as soon as that happens, and is staying in 9.0
|
||||
candidate.
|
||||
|
||||
### Records-related issues
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3226
|
||||
https://github.com/dotnet/csharplang/issues/3213
|
||||
https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
These are all records related issues. We didn't get all of what we want to accomplish in this space in C# 9, so we need to break the still-to-be-done parts of these
|
||||
proposals out into separate issues for 10 or later and close/move these.
|
||||
|
||||
### Primary Constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2691
|
||||
|
||||
We didn't managed to break this out of records in a satisfactory manner for 9, but we would like to get this in for 10. Retriaged to 10.0 Candidate.
|
||||
|
||||
### Proposal: Target typed null coalescing (`??`) expression
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2473
|
||||
|
||||
When attempting to spec and implement this, we found that it doesn't actually work well in practice, as `??` has a number of complicated rules around type resolution
|
||||
already and what part should be target-typed is confusing. We believe that, given the investigation, we won't be moving this one forward, and it moves to the Likely
|
||||
Never milestone.
|
||||
|
||||
### Champion: Simplified parameter null validation code
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2145
|
||||
|
||||
We didn't quite get the code gen for this one finished up. Moving to 10.0 candidate.
|
||||
|
||||
### Champion: relax ordering constraints around `ref` and `partial` modifiers on type declarations
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/946
|
||||
|
||||
We had put this in 9.0 because we thought it would be required for `data` classes. That did not end up being the case, and we don't have high priority for it otherwise.
|
||||
We'd accept a contribution if a community member wanted to loosen the restriction, thus moved to Any Time.
|
||||
|
||||
### Champion "Nullable-enhanced common type"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/33
|
||||
https://github.com/dotnet/csharplang/issues/881
|
||||
|
||||
We said when doing additional target-typing work that we either had to do these now, or it would be very complicated to implement later without avoiding breaking changes.
|
||||
Given that the target-typing we added more generally addresses this in most scenarios, we don't believe that the additional "break glass in case of emergency" bar is
|
||||
met with these, and they are moved to Likely Never.
|
||||
|
||||
### Proposal: improvements to nullable reference types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3297
|
||||
|
||||
This is part of the ongoing nullable improvements list. We will split the parts that have not been implemented out into a separate list, and then move this to 9.0 with
|
||||
just the things actually implemented for this release.
|
||||
|
||||
## Triage issues in C# 10.0 candidate
|
||||
|
||||
We will be moving issues from the 10.0 candidate bucket to the 10.0 Working Set bucket during triage. Issues in the 10.0 Working Set bucket are issues that we have decided
|
||||
to devote some design time towards during the upcoming 10.0 timeframe, but not all of the issues triaged into this working set will actually make it into the 10.0 release.
|
||||
|
||||
### Proposal: Exponentiation operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2585
|
||||
|
||||
We don't feel that this is a major priority for the language currently. It is a relatively small amount of work, however. If a community member wants to add a proposal
|
||||
for a specific symbol to use for the operator and a precedence for that symbol, we'd be happy to consider it at that point. Moved to Any Time.
|
||||
|
||||
### Champion "Type Classes (aka Concepts, Structural Generic Constraints)"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/110
|
||||
|
||||
We feel that issues in improving the type system, such as type classes, roles, statics in interfaces, etc, are the next big area of investment for the language. While
|
||||
we may not end up seeing the results of these investments in the 10.0 timeframe, we need to start designing them now or we'll never see them at all. As such, we are
|
||||
labeling this issue as "Long lead" to indicate that there is a lot of design work to come before and implementation can even start to happen. Moved to 10.0 Working Set.
|
||||
|
||||
### generic constraint: where T : ref struct
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1148
|
||||
|
||||
This is related to a forthcoming proposal on ref fields, as well as to the aforementioned type system improvements we want to make. To really be useful beyond allowing
|
||||
`Span<Span<T>>`, ref structs need to be able to implement interfaces, and so this will be considered in tandem with them. Moved to 10.0 Working Set.
|
||||
|
||||
### Champion "CallerArgumentExpression"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/287
|
||||
|
||||
While this could be considered Any Time, we already have most of an implementation and would like to get it in for C# 10, so we are moving it to 10.0 Working Set.
|
||||
|
||||
### Champion "Allow Generic Attributes"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/124
|
||||
|
||||
A community member is contributing this feature, and while we don't think there is any design work left to do from the language side there's still a bit of work on the
|
||||
implementation itself. We'll move this to 10.0 Working Set, as opposed to Any Time, to ensure that we give priority if there does end up being any language work to do.
|
||||
|
||||
### C# Feature Request: Allow value tuple deconstruction with default keyword
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1358
|
||||
|
||||
This is a small quality of life change that we already have a PR for. Let's get it merged, and move this issue to 10.0 Working Set.
|
||||
|
||||
### Expression Blocks/Switch Expression and Statement Enhancements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3086
|
||||
https://github.com/dotnet/csharplang/issues/3038
|
||||
https://github.com/dotnet/csharplang/issues/3087
|
||||
https://github.com/dotnet/csharplang/issues/2632
|
||||
|
||||
We know that we want to make some improvements here. We need to narrow down on a concrete proposal as we have a lot of different scopes/syntaxes currently, and then we
|
||||
can proceed with implementation. Move to 10.0 Working Set.
|
||||
|
||||
### Champion: Name lookup for simple name when target type known
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2926
|
||||
|
||||
We consider this a somewhat essential necessary feature for discriminated unions, so this should be considered in concert with that. Move to 10.0 Working Set.
|
||||
|
||||
### Proposal: "Closed" type hierarchies
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/485
|
||||
|
||||
This needs to be designed in concert with discriminated unions, so we'll move it to 10.0 Working Set.
|
||||
|
||||
### Championed: Target-typed implicit array creation expression `new[]`
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2701
|
||||
|
||||
This needs some design work around either making it a generalized feature for type parameter-based inference or if it specializes for the interfaces that array implements
|
||||
specifically. We don't feel that this particularly pressing, so we're moving it to X.0 unless we hear a need to add it to align with other .NET priorities.
|
||||
|
||||
### Champion: `base(T)` phase two
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2337
|
||||
|
||||
While this was part of the initial feature set for DIMs, we don't hear any particular customer complaints for this feature. We'll move it to X.0 until such time as we
|
||||
hear customer asks for it.
|
||||
|
||||
### Champion "defer statement"
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1398
|
||||
|
||||
LDM was somewhat negative on this feature. There are times when you'd want to pair code to run at the end of a block/method return, but defer has invisible consequences
|
||||
here because it would have to use try/finally. This give defer a penalty that would prevent it from being used in many of the places we'd otherwise want to use it ourselves.
|
||||
Additionally, there is significant negative community sentiment about this feature, much more than we usually get for participation on any particular csharplang issue. As a
|
||||
result, we are moving this feature to Likely Never, and if we see similar significant outcry to the rejection we can reconsider this.
|
||||
|
||||
### Five ideas for improving working with fixed buffers
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1502
|
||||
|
||||
This will be part of the previously-mentioned forthcoming ref fields proposal. Move it to 10.0 Working Set.
|
|
@ -1,111 +0,0 @@
|
|||
# C# Language Design Meeting for September 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Partial method signature matching](#partial-method-signature-matching)
|
||||
2. [Null-conditional handling of the nullable suppression operator](#null-conditional-handling-of-the-nullable-suppression-operator)
|
||||
3. [Annotating IEnumerable.Cast](#annotating-ienumerable.cast)
|
||||
4. [Nullability warnings in user-written record code](#nullability-warnings-in-user-written-record-code)
|
||||
5. [Tuple deconstruction mixed assignment and declaration](#tuple-deconstruction-mixed-assignment-and-declaration)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Now with warning waves it's a foam-covered baseball bat to hit people with"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Partial method signature matching
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/45519
|
||||
|
||||
There is a question about what amount of signature matching is required for method signatures, both as part of the expanded partial methods in C# 9
|
||||
and for the new `nint` feature in C# 9. Currently, our rules around what has to match and what does not are confusing: tuple names must match,
|
||||
`dynamic`/`object` do not have to match, we warn when there are unsafe nullability conversions, and other differences are allowed (including parameter
|
||||
names). We could lean on a warning wave here and make our implementation more consistent with the following general rules:
|
||||
|
||||
1. If there are differences in the CLR signature, we error (as we cannot emit code at all!)
|
||||
2. If there are differences in the syntactic signature, we warn (even for safe nullability changes).
|
||||
|
||||
While we like this proposal in general, we have a couple of concerns around compatibility. Tuple names erroring is existing behavior, and if we loosen
|
||||
that to a general warning that would need to be gated behind language version, as you could write code with a newer compiler in C# 8 mode that does not
|
||||
compile with the C# 8 compiler. This complicates implementation, and to make it simple we lean towards just leaving tuple name differences as an error.
|
||||
We also want to make sure that nullability is able to differ by obliviousness: a common case for generators is that either the original signature or the
|
||||
implementation will be unaware of nullable, and we don't want to break this scenario such that either both the user and generator must be nullable aware,
|
||||
or the must both be nullable unaware.
|
||||
|
||||
We also considered an extension to these rules where we make the new rules always apply to the new enhanced partial methods, regardless of warning level.
|
||||
However, we believe that this would result in a complicated user experience and would make the mental model harder to understand.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
1. We will keep the existing error that tuple names must match.
|
||||
2. We will keep the existing warnings about unsafe nullability differences.
|
||||
3. We will add a new warning wave warning for all other syntactic differences between partial method implementations.
|
||||
a. This includes differences like parameter names and `dynamic`/`object`
|
||||
b. This includes nullability differences where both contexts are nullable enabled, even if the difference is supposedly "safe" (accepting `null`
|
||||
where it is not accepted today).
|
||||
c. If nullability differs by enabled state (one part is enabled, the other part is disabled), this will be allowed without warning.
|
||||
|
||||
### Null-conditional handling of the nullable suppression operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3393
|
||||
|
||||
This is a spec bug that shipped with C# 8, where the `!` operator does not behave as a user would expect. Members of the LDT believe that this is broken
|
||||
on the same level as the `for` iterator variable behavior that was changed in C# 5, and we believe that we should take a similar breaking change to fix
|
||||
the behavior here. We have made a grammatical proposal for adjusting how null-conditional statements are parsed, and there was general agreement that this
|
||||
proposal is where we want to go. The only comment is that `null_conditional_operations_no_suppression` should be renamed to avoid confusion, as there can
|
||||
be a null suppression inside the term, just not at the end. A better name would be `null_conditional_operations_no_final_suppression`.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Accepted, with the above rename. Will get a compiler developer assigned to implement this ASAP.
|
||||
|
||||
### Annotating IEnumerable.Cast
|
||||
|
||||
https://github.com/dotnet/runtime/issues/40518
|
||||
|
||||
In .NET 5, the `Cast<TResult>` method was annotated on the return to return `IEnumerable<TResult?>`, which means that regardless of whether the input
|
||||
enumerable can contain `null` elements, the returned enumerable would be considered to contain `null`s. This resulted in some spurious warnings when
|
||||
upgrading roslyn to use a newer version of .NET 5. However, the C# in general lacks the ability to properly annotate this method for a combination of
|
||||
reasons:
|
||||
|
||||
1. There is no way to express that the nullability of one type parameter depends on the nullability of another type parameter.
|
||||
2. Even if there was a way to express 1, `Cast` is an extension method on `IEnumerable`, not `IEnumerable<T>`, because C# does not have partial type
|
||||
inference to make writing code in this scenario better.
|
||||
|
||||
Given this, we have a few options:
|
||||
|
||||
1. Leave the method as is, and possibly enhance the compiler/language to know about this particular method. This is analogous to the changes we're
|
||||
considering with `Where`, but it feels like a bad solution as it's not generalizable.
|
||||
2. Make the method return `TResult`, unannotated. The issue with this is that it effectively means the method might actually lie: there is no way to
|
||||
ensure that the method actually returns a non-null result if a non-null `TResult` is provided as a type, given that nullability is erased in the
|
||||
implementation. We're concerned that this could make the docs appear to lie, which we think would also give a bad experience.
|
||||
3. Convert `Cast` back to being unannotated. This seems to be compromise that both sides can agree on: analyzers can flag use of the unannotated API
|
||||
to help users, and spurious warnings get suppressed. It also matches the behavior of `IEnumerator.Current`, and means that the behavior of `foreach`
|
||||
loops over such a list behave in a consistent manner.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The BCL will make `Cast` and a few related APIs an oblivious API.
|
||||
|
||||
### Nullability warnings in user-written record code
|
||||
|
||||
The question here is on whether we should warn users when manually-implemented methods and properties for well-known members in a `record` should warn
|
||||
when nullability is different. For example, if their `Equals(R other)` does not accept `null`. There was no debate on this.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
we'll check user-defined `EqualityContract`, `Equals`, `Deconstruct`, ... methods on records for nullability safety issues in their declaration. For
|
||||
example, `EqualityContract` should not return Type?.
|
||||
|
||||
### Tuple deconstruction mixed assignment and declaration
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/125
|
||||
|
||||
We've discussed this feature in the past (https://github.com/dotnet/csharplang/blob/master/meetings/2016/LDM-2016-11-30.md#mixed-deconstruction), and
|
||||
we liked it then but didn't think it would fit into C# 7. It's been in Any Time since, and now we have a community PR. We have no concerns with
|
||||
moving forward with the feature.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Let's try and get this into C# 10.
|
|
@ -1,66 +0,0 @@
|
|||
# C# Language Design Meeting for September 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Required Properties](#required-properties)
|
||||
2. [Triage](#triage)
|
||||
|
||||
## Quote of the day
|
||||
|
||||
- "It's my version of thanks Obama, thanks C#"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Required Properties
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
[Presentation](./Required_Properties_2020_09_16.pdf)
|
||||
|
||||
Most of the time today was used hearing a presentation about a proposal for required properties. This presentation was intended
|
||||
as a high-level overview of the issue required properties are attempting to solve, and to illustrate a few approaches that could
|
||||
be taken for implementing these. Notes were not taken to the slide content itself, as the slides are linked above.
|
||||
|
||||
Overall, the LDM is a bit leary about the implementation complexity of runtime-verification methods. One approach that wasn't on
|
||||
the slides, but is a possible approach, is introducing validators and letting type authors do the verification themselves. This
|
||||
may end up resulting in type authors conflating nullability and requiredness for most simple implementations, but for the common
|
||||
scenarios this may be just fine. We were unable to find just one implementation strategy that stuck out as "this is the right thing
|
||||
to do." Instead, each implementation strategy had a set of tradeoffs, particularly around the implementation complexity of tracking
|
||||
whether a property has been initialized.
|
||||
|
||||
One thing that we noted from the presentation is that any method of introducing required properties is going to mean that types with
|
||||
these contracts on their empty constructors will not be able to be used in generic scenarios `where T : new()`, as the empty constructor
|
||||
is required to set some amount of properties. The presentation also did not cover how to do additive or subtractive contracts: ie, a
|
||||
future factory method `MakeAMads` might want to specify "Everything from the normal `Person` constructor except `FirstName`", whereas
|
||||
a copy constructor would want to specify "You don't have to initialize anything". Some work has started on designing this scenario,
|
||||
but it needs some more work before it's in a state to get meaningful feedback.
|
||||
|
||||
A read of the room found that most of the LDM was leaning towards compile-time only validation of required properties. It does mean
|
||||
that upgrading binary versions without recompiling can leave things in an invalid state, but the implementation strategies for
|
||||
getting actual runtime breaks are very complex and we're not sure they're worth the tradeoff. The next step in design will be to
|
||||
come back with a more concrete syntax proposal for how required properties will be stated, and how constructors will declare their
|
||||
contracts.
|
||||
|
||||
### Triage
|
||||
|
||||
We only had time left to triage 1 issue:
|
||||
|
||||
#### Proposal: Permit trailing commas in method signatures and invocations
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/391
|
||||
|
||||
This is a longstanding friction point in the language that has mostly positive sentiment on csharplang. We're also fairly confident
|
||||
that it wouldn't break any possible language features other than omitted arguments, which the LDM is not particularly interested
|
||||
in pursuing. That being said, the general gut reaction of most LDM members is something along the lines of "This looks wrong." We
|
||||
certainly acknowledge that, despite some syntactic misgivings, this can be a very useful feature, particularly for source generators
|
||||
and refactorings, as well as for just simply reordering parameters in a method call or definition. There are a couple of open issues
|
||||
we'd need to resolve as well:
|
||||
|
||||
* What would happen with param arrays?
|
||||
* Would tuple declarations be allowed to have trailing commas? This proposal actually seems like a natural way to allow oneples into
|
||||
the language, in a similar style to Python, where the oneple must have a trailing comma.
|
||||
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage in X.0. We think this feature has a place in C#, but we don't think it will make the bar for what we're interested in with C# 10.
|
|
@ -1,37 +0,0 @@
|
|||
# C# Language Design Meeting for September 23rd, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. General improvements to the `struct` experience
|
||||
|
||||
## Quote of the day
|
||||
|
||||
- "This makes C# developers queasy. Refs can never be null! The runtime says that's not true, the C# type system is a lie."
|
||||
|
||||
## Discussion
|
||||
|
||||
Most of this session was dedicated to explaining the proposal itself, which can be found [here](https://github.com/dotnet/csharplang/blob/master/proposals/low-level-struct-improvements.md).
|
||||
We covered the section `Provide ref fields` in full, but did not get beyond that in this session. What follows are some noted comments on the
|
||||
proposal, as we did not have long discussions about the merits of the feature itself.
|
||||
|
||||
* The general driving principles of this proposal are to ease friction points in the language around structs. Today we have `Span<T>`, which is
|
||||
a very useful type, but it uses a magic BCL-internal type called `ByReference<T>`. Span uses it to great effect, but because there are no compile
|
||||
rules around its safety we can't expose it publicly. This leads to 3rd parties using reflection to get it, which just results in badness all
|
||||
around. We'd like to allow the semantics to be fully specifiable in C# and enable it not just for the BCL, but for all types of structs. The LDM
|
||||
generally agrees with this goal.
|
||||
* There will need to be some metadata tool updates. In order for safe-to-escape analysis to work, we will have to ensure that all `ref` fields,
|
||||
even private fields, appear in reference assemblies and similar locations to ensure that the compiler can correctly determine lifetimes.
|
||||
* The rules around returning a ref to a ref parameter are not especially intuitive. We acknowledge that they are necessary given the lifetime
|
||||
rules today with methods returning a `Span`, but we could introduce a `CapturesAttribute` or something similar to indicate that a `ref` parameter
|
||||
is captured by the method, and thus allow passing it directly as a `ref` to `new Span<T>`.
|
||||
* There is a workaround for this behavior: instead of taking a `ref T` in the constructor, take a `Span<T>`, which will ensure that all the
|
||||
lifetimes line up. While this workaround is viable, we're somewhat worried it won't be straightforward enough of a solution.
|
||||
* Allowing `ref` assignment after the fact could be done in a later version of C#. It's a good deal of work in the compiler (likely an entirely
|
||||
new flow analysis pass) to correctly update the lifetimes, and we're not yet certain that the scenario is worth the effort. If this proves to be
|
||||
a friction point for users, we can revisit.
|
||||
* One scenario this spec does not consider is `ref` assignment in an object initializer, which is still part of the object construction phase.
|
||||
This should be allowable, and we need to update the draft specification to address this case.
|
||||
* `ref null` is going to be an annoying problem. Given that you can `default` structs in any of a number of ways, at some point it will be possible
|
||||
to observe a `default` ref struct that has a `null` `ref`. While the runtime does have an `Unsafe.IsNullRef` helper method, it feels unnatural that
|
||||
code that is entirely safe C# should have to use a method from the `Unsafe` class. Further, these newly-observable `null` `ref`s will will end up
|
||||
everywhere, in much the same way that `null` ends up everywhere. We may need to think more about this problem.
|
|
@ -1,207 +0,0 @@
|
|||
# C# Language Design Meeting for September 28th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Warning on `double.NaN`](#warning-on-double.nan)
|
||||
2. [Triage](#triage)
|
||||
1. [Proposal: more improvements to nullable reference types](#proposal-more-improvements-to-nullable-reference-types)
|
||||
2. [Proposal: Required Properties](#proposal-required-properties)
|
||||
3. [Proposal: Extend with expression to anonymous type](#proposal-extend-with-expression-to-anonymous-type)
|
||||
4. [Proposal: Shebang (#!) Support](#proposal-shebang--support)
|
||||
5. [Proposal: List Patterns](#proposal-list-patterns)
|
||||
6. [Proposal: Add ability to declare global usings for namespaces, types and aliases by using a command line switch](#proposal-add-ability-to-declare-global-usings-for-namespaces-types-and-aliases-by-using-a-command-line-switch)
|
||||
7. [Proposal: "Closed" enum types](#proposal-closed-enum-types)
|
||||
8. [Top-level functions](#top-level-functions)
|
||||
9. [Primary Constructors](#primary-constructors)
|
||||
10. [Champion: Simplified parameter null validation code](#champion-simplified-parameter-null-validation-code)
|
||||
11. [Proposal: Support generics and generic type parameters in aliases](#proposal-support-generics-and-generic-type-parameters-in-aliases)
|
||||
12. [Support for method parameter names in nameof](#support-for-method-parameter-names-in-nameof)
|
||||
|
||||
## Quote of the day
|
||||
|
||||
- "On a rational basis I have nothing against this"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Warning on `double.NaN`
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/15936
|
||||
|
||||
We have an existing FxCop warning (CA2242) for invalid comparisons to `double.NaN`. We could consider bringing
|
||||
that warning into the compiler itself as in a warning wave. However, as this analyzer is now shipped with the
|
||||
.NET 5 SDK, is on by default, and deals more with API usage than with the language itself, it would also be fine
|
||||
to leave it where it is.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Rejected. Leave the warning where it exists today.
|
||||
|
||||
### Triage
|
||||
|
||||
Today, we got through half the remaining issues in the C# 10.0 Candidate milestone
|
||||
|
||||
#### Proposal: more improvements to nullable reference types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3868
|
||||
|
||||
We have a few open topics in improvements to nullable reference types for the C# 10.0 timeframe. We're currently
|
||||
tracking LINQ improvements, Task-like type covariance, and uninitialized fields and constructors. This last point
|
||||
will likely be handled in conjunction with the proposals for required properties and initialization debt. The
|
||||
first two can be broken out to specific issues for the 10.0 timeframe.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
File separate issues for the first two bullets, and triage into the 10.0 working set.
|
||||
|
||||
#### Proposal: Required Properties
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3630
|
||||
|
||||
We've already started talking about this one.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into the 10.0 working set.
|
||||
|
||||
#### Proposal: Extend with expression to anonymous type
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3530
|
||||
|
||||
This proposal extends anonymous types to allow `with` expressions to change them, which we like a lot. In a way,
|
||||
this extension makes anonymous types practically just anonymous _record_ types, as they have the rest of the properties
|
||||
of a record already: value equality, ToString, etc. It also fits in well with the idea of generally extending `with`
|
||||
expressions to be more broadly applicable. Since anonymous types cannot be exposed as types in a public API, generating
|
||||
new `init` members and the `with` clone method is a non-breaking change. New compilations can take advantage of the
|
||||
features, and old compilations don't get affected by them.
|
||||
|
||||
As part of discussing this issue, we hit upon the idea of additionally allowing `new { }` to be target-typed. If a `new`
|
||||
expression that did not have `()` is assigned to something that matches its shape (such as a record with the correct)
|
||||
property names, we could just allow that new expression to be treated as the target-type constructor, rather than as
|
||||
creating a new anonymous type. If the target-type is `object` or `dynamic`, it will still result in an anonymous object
|
||||
being created, and there may be some tricks to figuring out generic inference, but we think it might be a path forward
|
||||
towards making target-typed new more regular with the rest of the language (a complaint we have already heard). A future
|
||||
proposal for that will be filed.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into C# 10.0 working set for consideration with the rest of the `with` extensions.
|
||||
|
||||
#### Proposal: Shebang (#!) Support
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3507
|
||||
|
||||
While could eventually be an interesting proposal, the tooling is not there currently, and we feel the discussion around
|
||||
developer ergonomics in .NET 6 will shape our discussions in this area.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into X.0, and if the .NET tooling looks to add `dotnet run csfile`, we can consider again at that point.
|
||||
|
||||
#### Proposal: List Patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3435
|
||||
|
||||
We like continuing to enhance the pattern support in C#, and are in general positive about this proposal. However, the
|
||||
somewhat-fractured nature of .NET here works to our detriment, not just in the `Count` vs `Length` property names, but
|
||||
in the general collection type sense. It would be nice to support `IEnumerable`, for example, which does not meet the
|
||||
definitions set out in the proposal. Another consideration would be dictionary types: we don't have support for a
|
||||
dictionary initializers specifically today, so having a decomposition step without a composition step would be odd, but
|
||||
they would be a useful pattern nontheless. We also would like to see if we can find a way to make the syntax use braces
|
||||
rather than square brackets to mirror collection initializers, though that will be difficult due to the empty property
|
||||
pattern.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We'll spend design time on this in the C# 10 timeframe, though it may not make the 10.0 release itself. Triaged into the
|
||||
10.0 working set.
|
||||
|
||||
#### Proposal: Add ability to declare global usings for namespaces, types and aliases by using a command line switch
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3428
|
||||
|
||||
This is, in many ways, a language-defining issue. The proposal itself is small, but it reflects the LDT's thinking of
|
||||
future C# directions as a whole. It is also very divisive, both in the LDT and in the greater community, with a small
|
||||
majority (both in the LDT and in the csharplang community) in favor of the feature. It introduces a level of magic to
|
||||
the language that has been somewhat resisted in the past. Additionally, there is concern that there is no one set of
|
||||
"base usings" that should be automatically included in files: a console app might only want to include `System`, while
|
||||
an ASP.NET app might want to include a bunch of namespaces for various routing properties, controllers, and the like.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We'll discuss this more in the 10.0 timeframe. There could be interactions around the theme of developer QoL in the .NET
|
||||
6 timeframe.
|
||||
|
||||
#### Proposal: "Closed" enum types
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3179
|
||||
|
||||
This proposal is linked to discriminated unions, in that it's a another type of closed hierarchy that C# does not support
|
||||
today.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into 10.0 working set, to be discussed with DU's and closed type hierarchies in general.
|
||||
|
||||
#### Top-level functions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3117
|
||||
|
||||
This is the follow-up work from C# 9 that we did not conclude in the initial push for top-level statements in C#,
|
||||
namely in allowing globally-accessible functions in C# to float at the top level without a containing class. One
|
||||
concern with generalizing this feature is that we have 20 years of BCL design that does not account for top-level
|
||||
functions, in addition to other well-known and used libraries. For consistency, these libraries would likely not
|
||||
use this new feature, which would relegate this feature to minimal usage. While many LDT members like the feature
|
||||
in general, and would likely introduce it if we were redesigning C# from the ground up, we don't believe that the
|
||||
feature belong in the C# we have today. We will continue to investigate whether top-level functions defined today
|
||||
(which are local functions to the implicit `Main` method) should be callable from within the current file, in order
|
||||
to have better compat with CSX. However, the overall feature is rejected.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected.
|
||||
|
||||
#### Primary constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2691
|
||||
|
||||
We have a proposal for this, and we are mostly in agreement that we should see this through.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into 10.0 working set.
|
||||
|
||||
#### Champion: Simplified parameter null validation code
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2145
|
||||
|
||||
We have an implementation of this mostly ready. It was done in such as way as to be usable by the BCL, and will
|
||||
potentially save 7K+ lines of code there. Let's get it in.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triage into 10.0 working set.
|
||||
|
||||
#### Proposal: Support generics and generic type parameters in aliases
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1239
|
||||
|
||||
We like the idea of improving aliases in general, and this could come into play when we talk about the previously-mentioned
|
||||
global usings. There are multiple possible flavors here though: there's simple aliases like we have today, that are
|
||||
freely convertible back to the underlying type, and then there are true opaque aliases that are not freely convertible
|
||||
back to the underlying type. Ideally, these latter aliases would be zero cost, which will likely require some work in
|
||||
conjunction with the runtime. Additionally, while we like the idea of making improvements here, we have a lot on the C# 10
|
||||
plate currently, and think it would fit in better with the type enhancements we hope to make after 10.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into X.0.
|
||||
|
||||
#### Support for method parameter names in nameof
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/373
|
||||
|
||||
We like this, and have the start of an implementation.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into 10.0 working set.
|
|
@ -1,134 +0,0 @@
|
|||
# C# Language Design Meeting for September 30th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. `record structs`
|
||||
1. [`struct` equality](#struct-equality)
|
||||
2. [`with` expressions](#with-expressions)
|
||||
3. [Primary constructors and `data` properties](#primary-constructors-and-data-properties)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "... our plan of record"
|
||||
- "haha badum-tish"
|
||||
|
||||
## Discussion
|
||||
|
||||
Our conversation today focused on bringing records features to structs, and specifically what parts should apply
|
||||
to _all_ structs, what should apply to theoretical `record` structs (if that's even a concept we should have), and
|
||||
what should not apply to structs at all, regardless of whether it's a `record` or not. The table we looked at is
|
||||
below, followed by detailed summaries of our conversations on each area. We did not come to a confirmed conclusion
|
||||
on primary constructors/data properties this meeting, but we do have a general room lean, which has been recorded.
|
||||
|
||||
|Feature |All structs |Record structs|No structs|
|
||||
|----------------------------------|--------------|--------------|----------|
|
||||
|Equality |--------------|--------------|----------|
|
||||
|- basic value equality | X (existing) | | |
|
||||
|- == and strongly-typed `Equals()`| | X | |
|
||||
|- `IEquatable<T>` | | X | |
|
||||
|- Customized value equality | X | | |
|
||||
|`with` |--------------|--------------|----------|
|
||||
|- General support | X | | |
|
||||
|- Customized copy | |explicit error| X |
|
||||
|- `with`ability abstraction | | ? | |
|
||||
|Primary constructors |--------------|--------------|----------|
|
||||
|- Mutability by default | | | leaning |
|
||||
|- Public properties | | leaning | |
|
||||
|- Deconstruction | | leaning | |
|
||||
|Data properties |--------------|--------------|----------|
|
||||
|- Mutability by default | | | leaning |
|
||||
|- Public properties | | leaning | |
|
||||
|
||||
### `struct` equality
|
||||
|
||||
`record`s in C# 9 are very `struct` inspired in their value equality: all fields in `record` a and b are compared
|
||||
for equality, and if they are all equal, then a and b are also equal. This is the default behavior for all `struct`
|
||||
types in C# today, if an `Equals` implementation is not provided. However, this default implementation is somewhat
|
||||
slow, as it uses reflection. We've talked in the past about potentially generating an `Equals` implementation for
|
||||
all struct types that would have better performance. However, we are definitely very concerned about potential size
|
||||
bloat for doing this, particularly around interop types. Given those concerns, we don't think we can generate such
|
||||
methods for all struct types. We then considered whether hypothetical `record` structs should get this implementation.
|
||||
However, generating a good implementation of equality for structs almost seems like it's not a C# language issue at
|
||||
all. More than just C# runs on the CLR, and it would be a shame if there was incentive to use a particular language
|
||||
because it generates a better equality method. Further, since we can't do this for all `struct` types, it means we
|
||||
would inevitably have to educate users that "`record struct`s generate better code for equality, so you may just
|
||||
want to make your `struct` a `record` for that reason alone", which isn't great either. Given that, we'd rather work
|
||||
with the runtime team to make the automatic `Equals` method better, which will benefit not just all C# structs,
|
||||
but all `struct` types from all languages that run on the CLR.
|
||||
|
||||
Next, we looked at whether we should expose new equality operators and strongly-typed `Equals` methods on `struct`
|
||||
types, as well as implementing `IEquatable<T>`. We again came to the conclusion that, for all existing `struct`
|
||||
types, it would be too costly in metadata (and a potential breaking change for exposing a strongly-typed `Equals`
|
||||
method or `IEquatable<T>`) to do this for all types. However, we do think that a gesture for opting into this
|
||||
generation would be useful. Given that, we considered whether it was useful to have these be more granular gestures,
|
||||
ie if a type could just opt-in to generating `IEquatable<T>` without the equality features. For these scenarios, we
|
||||
feel that the need just isn't there, and that it should be an all-or-nothing opt-in.
|
||||
|
||||
Finally on this topic, we considered customized `Equals` implementations. This is a fairly simple topic: all structs
|
||||
support customizing their definition of `Equals` today, and will continue to do so in the future.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
All structs will continue to use the runtime-generated `Equals` method if none is provided. Making a `struct` a
|
||||
`record` will be a way to opt-in to new surface area that uses this functionality. We will work with the runtime
|
||||
team to hopefully improve the implementation of the generated `Equals` methods in future versions of .NET.
|
||||
|
||||
### `with` expressions
|
||||
|
||||
We considered whether all structs should be copyable via a `with` expression, or just some subset of them. On the
|
||||
surface, this seems a simple question: all structs are copyable today, and we even have a dedicated CIL instruction
|
||||
for this: `dup`. It seems trivial to enable this for any location where we know the type is a `struct` type, and
|
||||
just emit the `dup` instruction. Where this becomes a more interesting question, though, is in the intersection
|
||||
between all structs and any potential for customization of the copy behavior. We have plans to enable `with`
|
||||
as a general pattern that any class can implement through some mechanism, and if structs can customize that behavior
|
||||
it means that a struct substituted for a generic type `where T : struct` will behave incorrectly if that behavior
|
||||
was customized. Additionally, if we extend `with`ability as a pattern and allow it to be expressed via some kind of
|
||||
interface method, would structs be able to implement that method? Or would it get an automatic implementation of
|
||||
that method?
|
||||
|
||||
An important note for structs is that, no matter what we do here with respect to `with`, structs are fundamentally
|
||||
different than classes as they're _already_ copied all the time. Unless someone is ensuring that they always pass
|
||||
a struct around via `ref`, the compiler is going to be emitting `dup`s all the time. While we could design a new
|
||||
runtime intrinsic to call either `dup` or the struct's clone method if it exists, struct cloning behavior has long-
|
||||
established semantics that we think users will continue to expect.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
All `struct` types should be implicitly `with`able. No `struct` types should be able to customize their `with`
|
||||
behavior. Depending on how we implement general `with` abstractions, `record` structs might be able to opt-in to them,
|
||||
but will still be unable to customize the behavior of that abstraction.
|
||||
|
||||
### Primary constructors and `data` properties
|
||||
|
||||
Finally today, we considered the interactions of primary constructors, data properties, and structs. There are two
|
||||
general ideas here:
|
||||
|
||||
1. `struct` primary constructors should mean the same thing as `class` primary constructors (with whatever behavior
|
||||
we define later in this design cycle), and `record struct` primary constructors should mean the same thing as
|
||||
`record` primary constructors (public init-only properties), or
|
||||
2. `record struct` primary constructors should mean public, mutable fields.
|
||||
|
||||
Option 1 would provide a symmetry between record structs and record class types, while option 2 would provide a
|
||||
symmetry between record structs and tuple types. In a sense, a record struct would just become a strongly-named tuple
|
||||
type, and have all the same behaviors as a standard tuple type. You could then opt a record struct into being
|
||||
immutable by declaring the whole type `readonly`, or declaring the individual parameters `readonly`. For example:
|
||||
|
||||
```cs
|
||||
// Public, mutable fields named A and B
|
||||
record struct R1(int A, int B);
|
||||
|
||||
// Public, readonly fields named A and B
|
||||
readonly record struct R2(int A, int B);
|
||||
```
|
||||
|
||||
A key point in the mutability question for structs is that mutability in a struct type is nowhere near as bad as
|
||||
mutability in a reference type. It can't be mutated when it's the key of a dictionary, for example, and unless
|
||||
refs to the struct are being passed around the user is always in control of the struct. Further, if a ref is passed
|
||||
and it is saved elsewhere, that's a copy, and mutation to that copy doesn't affect the original. As always, we also
|
||||
have easy syntax to make something readonly in C#, while not having an easy syntax for making it mutable. On the other
|
||||
hand, the shortest syntax in a class record type is to create an immutable property, and it might be confusing if
|
||||
we had differing behaviors between record classes and record structs.
|
||||
|
||||
We did not come to a conclusion on this topic today. A general read of the room has a _slight_ lean towards keeping
|
||||
the behavior consistent with record classes, but a number of members are undecided as there are good arguments in
|
||||
both directions. We will revisit this topic in a future meeting after having some time to mull over the options here.
|
|
@ -1,71 +0,0 @@
|
|||
# C# Language Design Meeting for October 5th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [`record struct` primary constructor defaults](#record-struct-primary-constructor-defaults)
|
||||
2. [Changing the member type of a primary constructor parameter](#changing-the-member-type-of-a-primary-constructor-parameter)
|
||||
3. [`data` members](#data-members)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "The problem with people who voted for immutable by default is that they can't change their opinion. #bad_dad_jokes"
|
||||
|
||||
## Discussion
|
||||
|
||||
### `record struct` primary constructor defaults
|
||||
|
||||
We picked up today where we left off [last time](LDM-2020-09-30.md#primary-constructors-and-data-properties), looking at what
|
||||
primary constructors should generate in `record struct`s. We have 2 general axes to debate: whether we should generate mutable
|
||||
or immutable members, and whether those members should be properties or fields. All 4 combinations of these options are valid
|
||||
places that we could land, with various pros and cons, so we started by examing the mutable vs immutable axis. In C# 9, `record`
|
||||
primary constructors mean that the properties are generated as immutable, and consistency is a strong argument for preferring
|
||||
immutable in structs. However, we also have another analogous feature in C#: tuples. We decided on mutability there because it's
|
||||
more convenient, and struct mutability does not carry the same level of concern as class mutability does. A struct as a
|
||||
dictionary key does not risk getting lost in the dictionary unless it itself references mutable class state, which is just as
|
||||
much of a concern for class types as it is for struct types. Even if we had `with` expressions at the time of tuples, it's
|
||||
likely that we still would have had the fields be mutable. A number of C# 7 features centered around reducing unnecessary struct
|
||||
copying, such as `readonly` members and ref struct improvements, and reducing copies in large structs by `with` is still a
|
||||
useful goal. Finally, we have a better story for making a `struct` fully-`readonly` with 1 keyword, while we don't have a similar
|
||||
story for making a `struct` fully-mutable with a similar gesture.
|
||||
|
||||
Next, we examined the question of properties vs fields. We again looked to our previous art in tuples. `ValueTuple` can be viewed
|
||||
as an anonymous struct record type: it has value equality and is used as a pure data holder. However, `ValueTuple` is a type
|
||||
defined in the framework, and its implementation details are public concern. As a framework-defined pure data holder, it has no
|
||||
extra behavior to encapsulate. A `record struct`, on the other hand, is not a public framework type. Much like any other user-
|
||||
defined `class` or `struct`, the implementation details are not public concern, but the concern of the creator. We have real
|
||||
examples in the framework (such as around the mathematics types) where exposing fields instead of properties was later regretted
|
||||
because it limits the future flexibility of the type, and we feel the same level of concern applies here.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Primary constructors in `record struct`s will generate mutable properties by default. Like with `record` `class`es, users will
|
||||
be able to provide a definition for the property if they do not like the defaults.
|
||||
|
||||
### Changing the member type of a primary constructor parameter
|
||||
|
||||
In C# 9, we allow `record` types to redefine the property generated by a primary constructor parameter, changing the accessibility
|
||||
or the accessors. However, we did not allow them to change whether the member is a field or property. This is an oversight, and
|
||||
we should allow changing whether the member is a field or property in C# 10. This will allow overriding of the default decision
|
||||
in the first section, giving an ability for a "grow-up" story for tuples into named `record struct`s with mutable fields if the
|
||||
user wishes.
|
||||
|
||||
### `data` members
|
||||
|
||||
Finally today, we took another look at `data` members, and what behavior they should have in `record struct`s as opposed to
|
||||
`record` `classes`. We had previously decided that `data` members should generate `public` `init` properties in `record` types;
|
||||
therefore, the crucial thing to decide is if `data` should mean the same thing as `record` would in that type, or if the `data`
|
||||
keyword should be separated from `record` entirely. In C# today, we have very few keywords that change the code they generate
|
||||
based on containing type context, and making `data` be dependent on whether the member is in a `struct` or `class` could end up
|
||||
being quite confusing. On the other hand, if `data` is "the short way to create a nominal record type", then having different
|
||||
behavior between positional parameters and `data` members in a `struct` could also be confusing.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We did not reach a decision on this today. There are 3 proposals on the table:
|
||||
|
||||
1. A `data` member is `public string FirstName { get; set; }` in `struct` types, and `public string FirstName { get; init; }` in
|
||||
`class` types.
|
||||
2. A `data` member is `public string FirstName { get; init; }` in all types.
|
||||
3. We cut `data` entirely.
|
||||
|
||||
We'll come back to this in a future LDM.
|
|
@ -1,103 +0,0 @@
|
|||
# C# Language Design Meeting for October 7th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [`record struct` syntax](#record-struct-syntax)
|
||||
2. [`data` members redux](#data-members-redux)
|
||||
3. [`ReadOnlySpan<char>` patterns](#readonlyspanchar-patterns)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "And we're almost there (famous last words, I'll knock on something)"
|
||||
- "What about `record delegate`... does `record interface` make sense... I'll go away now"
|
||||
|
||||
## Discussion
|
||||
|
||||
### `record struct` syntax
|
||||
|
||||
First, we looked at what syntax we would use to specify `struct` types that are records. There are two possibilities:
|
||||
|
||||
```cs
|
||||
record struct Person;
|
||||
struct record Person;
|
||||
```
|
||||
|
||||
In the former, `record` is the modifier on a `struct` type. In the latter, `struct` is the modifier on `record` types.
|
||||
We also considered whether to allow `record class`.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
By unanimous agreement, `record struct` is the preferred syntax, and `record class` is allowed. `record class` is
|
||||
synonymous with just `record`.
|
||||
|
||||
### `data` members redux
|
||||
|
||||
With `data`, we come back to the same question we had at the end of [Monday](LDM-2020-10-05.md#data-members): should we have
|
||||
`data` members, and if we do, what should they mean. `data` can be viewed as a strategy to try and get nominal records in a
|
||||
single line, much like positional records. This is not a goal of brevity just for brevity's sake: in discriminated unions,
|
||||
listing many variants as nominal records is a design goal, and single-line declarations are particularly useful for this case.
|
||||
Many languages with discriminated unions based on inheritance introduce short class-declaration syntax for use in union
|
||||
declarations, and that was a defining goal for where `record` types would be useful.
|
||||
Another area worth examining is the original proposal for the `data` keyword. In the original proposal, primary constructors
|
||||
would have meant the same thing in `record` types as well non-`record` types, and `data` would have been used as a modifier
|
||||
on the primary constructor parameter to make it a public get/init property. `data` applied to a member, then, would have been
|
||||
a natural extension and reuse of that keyword. With the original scenario gone, there are a few concrete scenarios we think
|
||||
are highly related to, and will influence, the `data` keyword:
|
||||
|
||||
1. Use as a single-line in a discriminated union. While this is motivating, it's worth considering that, at least to some LDT
|
||||
members, anything more than 2 properties as `data` members doesn't look great, and would perhaps work better as a multi-line
|
||||
construct, which will line up visually. This seems a reasonable concern, so `data` may not be the solution we're looking for.
|
||||
2. Use in required properties, as it seems likely that we will need some keyword to indicate that a property is a required
|
||||
member in a type. While `data` could be orthogonal to some other required property keyword, it's likely there will be at least
|
||||
some interaction as we'd likely want `data` to additionally imply required. It's also possible that we could have just a single
|
||||
keyword to mark a property required, and it gives you what we believe are the useful defaults for such a property.
|
||||
3. Use in general primary constructors as a way to say "give me the same thing a `record` would have for this parameter". This
|
||||
would be somewhat resurrecting the original use case for the keyword, as we could then retcon `record` primary constructors
|
||||
as implying `data` on all parameters for you, but you can then opt-in to them in regular primary constructors as well. `data`
|
||||
members in a class, then, would again become a natural reuse and extension of the keyword on a primary constructor parameter.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll leave `data` out of the language for now. Our remaining motivating scenarios are things that will be worked on more in
|
||||
C# 10 cycle, and absent clearer designs in those spaces the need and design factors for `data` are too abstract. Once we work
|
||||
more on these 3 scenarios, we'll revisit `data` and see if a need for it has emerged.
|
||||
|
||||
### `ReadOnlySpan<char>` patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1881
|
||||
|
||||
We have a community PR to implement the Any Time feature of allowing constant strings to be used to pattern match against
|
||||
a `ReadOnlySpan<char>`. This would be acknowledging special behavior for `ReadOnlySpan<char>` with respect to constant strings,
|
||||
but we already have acknowledged special behavior for `Span` and `ReadOnlySpan` in the language, around `foreach`. We also
|
||||
considered whether this could be a breaking change, but we determined it could not be one: `ReadOnlySpan` cannot be converted
|
||||
to `object` or to an interface as it is a ref struct, so if there exists a pattern today operating on one today the input type
|
||||
of that pattern match must be `ReadOnlySpan<char>`. There were two open questions:
|
||||
|
||||
#### Should we allow `Span<char>`
|
||||
|
||||
The question is if `Span<char>` should also be allowed as well as `ReadOnlySpan<char>`. All of the same arguments about
|
||||
compat apply to `Span<char>`. We also considered whether `Memory`/`ReadOnlyMemory` should be allowed inputs. Unlike `Span`/`ReadOnlySpan`,
|
||||
though, there are backcompat concerns with `Memory` that cannot be overlooked, as they are not ref structs. It is very
|
||||
easy to obtain a `Span`/`ReadOnlySpan` from a `Memory`/`ReadOnlyMemory`, however, so the need isn't as great.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
`Span<char>` is allowed. `Memory<char>`/`ReadOnlyMemory<char>` are not.
|
||||
|
||||
#### Is this specific to `switch`, or can it be any pattern context
|
||||
|
||||
The original proposal here just mentioned `switch`. However, the implementation allows it to be used in any pattern context,
|
||||
such as `is`.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Allowed in all pattern contexts.
|
||||
|
||||
|
||||
#### Final Notes
|
||||
|
||||
We also discussed making sure that switching on a `Span`/`ReadOnlySpan` is as efficient for large switch statements as switching
|
||||
on a string is. Over 6 cases, the compiler will take the hashcode of the string and use it to implement a jump table to reduce the
|
||||
number of string comparisons necessary. This method is added to the `PrivateImplementationDetails` class in an assembly, and we
|
||||
should make sure to do the same thing for `Span<char>` and `ReadOnlySpan<char>` here as well so the cost to using one isn't higher
|
||||
than allocations would be from just doing a substring and matching with that.
|
|
@ -1,57 +0,0 @@
|
|||
# C# Language Design Meeting for October 12th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. General improvements to the `struct` experience (continued)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Only 5 people are going to use it..."
|
||||
- "We said that about function pointers too."
|
||||
|
||||
## C# Community Ambassadors
|
||||
|
||||
We have been taking a look at https://github.com/dotnet/csharplang/discussions/3878, which is around how to help community
|
||||
proposals into better shape to be looked at by LDM. We largely agree with the points raised, which is that most community
|
||||
issues are in a state that isn't quite good enough to be sponsored by an LDT member, but also not controversial or generally
|
||||
discouraged enough to be outright rejected. Our move to enabling discussions on the repo itself was the first step in this
|
||||
direction: discussions are more free-form, allow multiple branching conversations, and we view them as having less requirements
|
||||
towards creating one. The next step we're taking today is nominating a few community members to become ambassadors to the
|
||||
community: @jnm2, @YairHalberstadt, and @svick. This role will be focussed around helping triage incoming issues and
|
||||
discussions, helping community members get their proposals into a state that can realistically be looked at by LDT members
|
||||
and potentially championed, and helping with deduplication as it is noticed. We're starting very small with this experiment:
|
||||
if it proves successful, we can consider expanding the list to more members of the csharplang community, of which there are
|
||||
several deserving candidates. As part of this, we're tentatively hoping to review promising community proposals at a more
|
||||
regular cadence, hopefully monthly.
|
||||
|
||||
Community ambassadors are not members of the LDT and do not have the ability to champion issues. They will help us look at
|
||||
deserving community proposals, and we value their input, as we value the input of the general community here.
|
||||
|
||||
## Discussion
|
||||
|
||||
Today, we finished going through the fixed fields proposal, found [here](https://github.com/dotnet/csharplang/blob/master/proposals/low-level-struct-improvements.md).
|
||||
[Previously](LDM-2020-09-23.md), we made it through the `Provide ref fields` section of the specification. Today, we
|
||||
finished going through the rest. Again, most the conversation was dedicated to the specification itself, but there were
|
||||
a few points brought up that will be updated in the specification later:
|
||||
|
||||
* `ThisRefEscapes` is defined very narrowly in this proposal, not allowed on any virtual methods (including methods from
|
||||
interfaces). In our initial investigations, we don't see a huge need for allowing it on interface methods. We can consider
|
||||
this in the future if it ends up being a friction point, but will need a good amount of work around ensuring that OHI is
|
||||
correctly respected.
|
||||
* We considered the issue of whether we should use syntax for `ThisRefEscapes` and `DoesNotEscape`, and nearly-unanimously
|
||||
decided on using attributes. Attributes allow us to have a more descriptive name that users are less likely to accidentally
|
||||
use. Further, all this attribute is controlling is a `modreq`, not the actual implementation of the method. We have existing
|
||||
attributes such as `SpecialNameAttribute` that control emit flags like this, so it's not unprecedented.
|
||||
* The syntax for fixed buffer locals is actually quite generally attractive: it would be nice if we could remove the requirement
|
||||
for specifying `fixed` in fields. It would further simplify the language: we even have parser code that parses this form today
|
||||
so that we can give nicer errors to people coming from C/C++. It would further resolve an ambiguity: in the proposal today, old-
|
||||
style fixed-size buffers are differentiated from new-style by whether or not the field is in an unsafe context: by omitting the
|
||||
fixed, we have a completely different syntax that is unambiguous.
|
||||
* We don't believe there is any real motivating scenario for either fixed multi-dimensional arrays or fixed jagged arrays of a
|
||||
specific inner length. Jagged arrays of this form would work: `int[] array[10]`, where you have a fixed buffer of array references,
|
||||
but allocating the inner array as part of the containing structure itself isn't currently seen as an important scenario.
|
||||
Multidimensional arrays today need to call into CLR helper methods today and are generally slower. We can think about this later
|
||||
if a scenario comes up.
|
||||
* We might want to make "inline array" a first-class type in the CLR. This would allow for things such as substituting in type
|
||||
parameters. This will largely be driven by the CLR design here.
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
# C# Language Design Meeting for October 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Triage](#triage)
|
||||
1. [Repeated Attributes in Partial Members](#repeated-attributes-in-partial-members)
|
||||
2. [Permit a fixed field to be declared inside a readonly struct](#permit-a-fixed-field-to-be-declared-inside-a-readonly-struct)
|
||||
3. [Do not require fixing a fixed field of a ref struct](#do-not-require-fixing-a-fixed-field-of-a-ref-struct)
|
||||
4. [params Span<T>](#params-spant)
|
||||
5. [Sequence Expressions](#sequence-expressions)
|
||||
6. [utf8 string literals](#utf8-string-literals)
|
||||
7. [pattern-based `with` expressions](#pattern-based-with-expressions)
|
||||
8. [Property improvements](#property-improvements)
|
||||
9. [File scoped namespaces](#file-scoped-namespaces)
|
||||
10. [Discriminated Unions](#discriminated-unions)
|
||||
11. [Efficient params and string formatting](#efficient-params-and-string-formatting)
|
||||
12. [Allow omitting unused parameters](#allow-omitting-unused-parameters)
|
||||
2. [Milestone Simplification](#milestone-simplification)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I used to have a fidget cube, but then [redacted] took away my fidget cube because it was apparently a very annoying device for everyone else"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Triage
|
||||
|
||||
#### Repeated Attributes in Partial Members
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3658
|
||||
|
||||
We discussed this briefly at the tail-end of C# 9 work, and came to the conclusion this is an issue for source generator authors and that we wanted
|
||||
to make it work. Triaged into the working set.
|
||||
|
||||
#### Permit a fixed field to be declared inside a readonly struct
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1793
|
||||
|
||||
This is part of the feature we discussed Monday. Triage into the working set.
|
||||
|
||||
#### Do not require fixing a fixed field of a ref struct
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1792
|
||||
|
||||
We generally like the idea of this feature: language-wise it's small, makes sense, and is an annoyance for users of ref structs. However, the
|
||||
implementation of `fixed` in Roslyn has historically been an issue with a long bug trail coming every time we need to make a change. We think
|
||||
this makes sense the next time we need to a larger feature around fixed that would force us to refactor the handling of `fixed` in the compiler.
|
||||
Until then, we don't think the implementation cost is worth it. Triaged to the backlog.
|
||||
|
||||
#### params Span<T>
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1757
|
||||
|
||||
We like this feature. We'll need to carefully design the overload resolution rules such that it wins out over existing params arrays. Libraries
|
||||
can then intentionally opt-in by introducing `Span<T>` overloads, just like they can introduce any new overload today that causes a change in
|
||||
behavior when recompiled. Triaged into the working set.
|
||||
|
||||
#### Sequence Expressions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/377
|
||||
|
||||
Related to #3038, #3037, and #3086, which are all in the working set. We'll be taking a look at the whole scenario in the upcoming design period,
|
||||
so this is triaged into the working set with the others.
|
||||
|
||||
#### utf8 string literals
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/184
|
||||
|
||||
We need the runtime to make progress here. While we could consider ways to make it easier to declare constant utf-8 byte arrays, we feel that
|
||||
would likely box us in when the runtime wants to move forward in this area. When they're ready, we can put this back on the agenda. Triaged
|
||||
into the backlog.
|
||||
|
||||
#### pattern-based `with` expressions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/162
|
||||
|
||||
This is part of the next round of record work, so into the working set it goes. We may need to think about how general object might be able to
|
||||
do object reuse as part of a `with` (Roslyn would not be able use it to replace `BoundNode.Update` as spec'd for records, for example), so that
|
||||
is a scenario we need to keep in mind as we generalize.
|
||||
|
||||
#### Property improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/133
|
||||
https://github.com/dotnet/csharplang/issues/140
|
||||
|
||||
We've discussed these recently in LDM. We're moving forward with design, starting with #133. Triaged into the working set.
|
||||
|
||||
#### File scoped namespaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/137
|
||||
|
||||
Triaged into the working set. We'll need to come up with a more complete proposal than we have currently, particularly considering how it will
|
||||
interact with top-level statements, but it doesn't seem too difficult.
|
||||
|
||||
#### Discriminated Unions
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/113
|
||||
|
||||
This is one of the next big C# tent poles we're looking to address, and we're working on an updated proposal after having some time to ruminate
|
||||
on the previous proposals in the area and post initial records. We have lots of design work to do, so into the working set it goes.
|
||||
|
||||
#### Efficient params and string formatting
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2302
|
||||
|
||||
Interpolated strings are a pit of failure in certain scenarios, such as logging, where formatting costs are incurred up front even if they're
|
||||
not needed. We'll keep this in the working set to keep iterating on proposals. We know we want to do some work here, but we're not sure exactly
|
||||
how it will function yet.
|
||||
|
||||
#### Allow omitting unused parameters
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/2180
|
||||
|
||||
We had some initial questions around the metadata representation of discards: should the be nameless, for example? We generally like proposals
|
||||
that put discards in more places, and we're willing to look at a complete proposal if one is presented. Triaged to any time.
|
||||
|
||||
### Milestone Simplification
|
||||
|
||||
Today, we have quite a few milestones that mean various things at various points in time, and it's hard for outsiders to keep track of what is
|
||||
on track for what. This is further complicated by the fact that sometimes things are just not known: we could be working on a feature with every
|
||||
intention to put it in the next version of C#, but fully acknowledging it might not make it. Or we may be doing design work for a feature that
|
||||
we know _definitely_ will not make it into the next version of C#, but needs to have work done or we'll never get it at all. For this reason,
|
||||
we're simplifying our milestones to be more clear about what the state of things is:
|
||||
|
||||
* Working Set is the set of proposals that the LDT is currently actively working on. Not everything in this milestone will make the next version
|
||||
of C#, but it will get design time during the upcoming release.
|
||||
* Backlog is the set of proposals that members of the LDT have championed, but are not actively working on. While discussion and ideas from the
|
||||
community are welcomed on these proposals, the cost of the design work and implementation review on these features is too high for us to consider
|
||||
community implementation until we are ready for it.
|
||||
* Any Time is the set of proposals that members of the LDT have championed, but are not being actively worked on and are open to community
|
||||
implementation. We'll go through these shortly and label the ones that need to have a specification added vs the ones that have an approved spec
|
||||
and just need implementation work. Those that need a specification still need to be presented during LDM for approval of the spec, but we are
|
||||
willing to take the time to do so at our earliest convenience.
|
||||
* Likely Never is the set of proposals that the LDM has reject from the language. Without strong need or community feedback, these proposals will
|
||||
not be considered in the future.
|
||||
* Numbered milestones are the set of features that have been implemented for that particular language version. For closed milestones, these are
|
||||
the set of things that shipped with that release. For open milestones, features can be potentially pulled later if we discover compatability or
|
||||
other issues as we near release.
|
|
@ -1,132 +0,0 @@
|
|||
# C# Language Design Meeting for October 21st, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Primary Constructors](#primary-constructors)
|
||||
2. [Direct Parameter Constructors](#direct-parameter-constructors)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Hopefully Seattle doesn't wash into the ocean"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Primary Constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4025
|
||||
|
||||
We started today by examining the latest proposal around primary constructors, and attempting to tease out the possible
|
||||
behaviors of what a primary constructor could mean. Given this sample code:
|
||||
|
||||
```cs
|
||||
public class C(int i, string s) : B(s)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
there are a few possible behaviors for what those parameters mean:
|
||||
|
||||
1. Parameter references are only allowed in initialization contexts. This means you could assign them to a property, but
|
||||
you couldn't use them in a method that runs after the class has been initialized.
|
||||
2. Parameter references are allowed throughout the class, and if they're referenced from a non-initialization context then
|
||||
they are captured in the type, but are not considered fields from a language perspective. This is the proposed behavior in
|
||||
the linked discussion.
|
||||
3. Parameter references are automatically captured to fields of the same name.
|
||||
|
||||
We additionally had a proposal in conjunction with behavior 1: You can opt in to having a member generated by adding an
|
||||
accessibility to the parameter. So `public class C(private int i)` would generate a private field `i`, in addition to having
|
||||
a constructor parameter. This is conceivably not tied to behavior 1 however, as it could also apply to behavior 2 as well.
|
||||
It would additionally need some design work around what type of member is generated: would `public` generate a field or a
|
||||
property? Would it be mutable or immutable by default?
|
||||
|
||||
To try and come up with a perferred behavior here, we started by taking a step back and examining the motivation behind
|
||||
primary constructors. Our primary (pun kinda intended) motivation is that declaring a property and initializing it from
|
||||
a constructor is a boring, repetitive, boilerplate-filled process. You have to repeat the type twice, and repeat the name
|
||||
of the member 4 times. Various IDE tools can help with generating these constructors and assignments, but it's still a lot
|
||||
of boilerplate code to read, which obscures the actually-interesting bits of the code (such as validation). However, it is
|
||||
_not_ a goal of primary constructors to get to 1-line classes: we feel that this need is served by `record` types, and that
|
||||
actual classes are going to have some behavior. Rather, we are simply trying to reduce the overhead of describing the simple
|
||||
stuff to let the real behavior show through more strongly.
|
||||
|
||||
With that in mind, we examined some of the merits and disadvantages of each of these:
|
||||
1. We like that parameters look like parameters, and adding an accessibility makes it no longer look like a parameter. There's
|
||||
definitely a lot to debate on what that accessibility should actually do though. There are some concerns that having the
|
||||
parameter not be visible is non-obvious to users: to solve this, we could make then visible throughout the type, but have it
|
||||
be an error to reference in a location that is not an initialization context (and a codefix to add an accessibility to make
|
||||
it very easy to fix). This allows users to be very explicit about the lifetime of variables.
|
||||
2. This variation of the proposal might feel more natural to users, as the variable exists in an outer "scope" and is therefore
|
||||
visible to all inner scopes. There is some concern, however, that silent captures could mean that the state of a class is no
|
||||
longer visible: you'll have to examine all methods to determine if a constructor parameter is captured, which could be suboptimal.
|
||||
3. This the least flexible of the proposals, and wasn't heavily discussed. It would need some amount of work to fit in with the
|
||||
common autoprop case, where the others could work without much work (either via generation or by simple assignment in an
|
||||
initializer for the autoprop).
|
||||
|
||||
In discussing this, we brought another potential design: we're considering primary constructors to eliminate constructor boilerplate.
|
||||
What if we flipped the default, and instead generated a constructor based on the members, rather than generating potential members
|
||||
based on a constructor. A potential strawman syntax would be something like this:
|
||||
```cs
|
||||
// generate constructor and assignments for A and B, because they are marked default
|
||||
public class C
|
||||
{
|
||||
default public int A { get; }
|
||||
default public string B { get; }
|
||||
}
|
||||
```
|
||||
There are a bunch of immediate questions around this: how does ordering work? What if the user has a partial class? Does this
|
||||
actually solve the common scenario? While we think the answer to this is no, it does bring up another proposal that we
|
||||
considered in the C# 9 timeframe while considering records: Direct Parameter Constructors.
|
||||
|
||||
## Direct Parameter Constructors
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4024
|
||||
|
||||
This proposal would allow constructors to reference members defined in a class, and the constructor would then generate a matching
|
||||
parameter and initialization for that member in the body of the constructor. This has some benefits, particularly for class types:
|
||||
|
||||
* Many class types have more than one constructor. It's not briefer than primary constructors declaring members for a class with
|
||||
just one constructor, but it does get briefer as you start adding more constructors.
|
||||
* We believe, at least from our initial reactions, that this form would be easier to understand than accessibility modifiers on the
|
||||
parameters.
|
||||
|
||||
There are still some open questions though. You'd like to be able to use this feature in old code, but if we don't allow for customizing
|
||||
the name of the parameter, then old code won't be able to adopt this for properties, as properties will almost certainly have different
|
||||
casing than the parameters in languages with casing. This isn't something we can just special case for the first letter either: there
|
||||
are many examples (even in Roslyn) of identifiers that have the first two letters capitalized in a property and have them both lowercase
|
||||
in a parameter (such as `csharp` vs `CSharp`). We briefly entertained the idea of making parameter names match in a case-insensitive
|
||||
manner, but quickly backed away from this as case matters in C#, working with casing in a culture-sensitive way is a particularly hard
|
||||
challenge, and wouldn't solve all cases (for example, if a parameter name is shortened compared to the property).
|
||||
|
||||
We also examined how this feature might interact with the accessibility-on-parameter proposal in the previous section. While they are
|
||||
not mutually exclusive, several members of the LDT were concerned that having both of these would add too much confusion, giving too
|
||||
many ways to accomplish the same goal. A read of the room found that we were unanimously in favor of this proposal over the accessibility
|
||||
proposal, and there were no proponents of adding both proposals to the language.
|
||||
|
||||
Finally, we started looking at how initialization would work with constructor chaining. Some example code:
|
||||
|
||||
```cs
|
||||
public class Base {
|
||||
public object Prop1 { get; set; }
|
||||
public virtual object Prop2 { get; set; }
|
||||
public Base(Prop1, Prop2) { Prop2 = 1; }
|
||||
}
|
||||
|
||||
public class Derived : Base
|
||||
{
|
||||
public new string Prop1 { get; set; }
|
||||
public override object Prop2 { get; set; }
|
||||
public Derived(Prop1, Prop2) : base(Prop1, Prop2) { }
|
||||
}
|
||||
```
|
||||
|
||||
Given this, the question is whether the body of `Derived` should initialize `Prop1` or `Prop2`, or just one of them, or neither of them.
|
||||
The simple proposal would be that passing the parameter to the base type always means the initialization is skipped, but that would
|
||||
mean that the `Derived` constructor has no way to initialize the `Prop1` property, as it can no longer refer to the constructor parameter
|
||||
in the body, and `Base` certainly couldn't have initialized it (since it is not visible there). There are a few questions like this that
|
||||
we'll need to work out.
|
||||
|
||||
## Conclusions
|
||||
|
||||
Our conclusions today are that we should pursue #4024 in ernest, and come back to primary constructors with that in mind. Several members
|
||||
are not convinced that we need primary constructors in any form, given that our goal is not around making regular `class` types have only
|
||||
one line. Once we've ironed out the questions around member references as parameters, we can come back to primary constructors in general.
|
|
@ -1,109 +0,0 @@
|
|||
# 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 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.
|
|
@ -1,60 +0,0 @@
|
|||
# C# Language Design Meeting for November 4th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Nullable parameter defaults](#nullable-parameter-defaults)
|
||||
2. [Argument state after call for AllowNull parameters](#argument-state-after-call-for-allownull-parameters)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
No particularly amusing quotes were said during this meeting, sorry.
|
||||
|
||||
## Discussion
|
||||
|
||||
### Nullable parameter defaults
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/4101
|
||||
|
||||
We started today by examining some declaration cases around nullable that we special cased in our our initial implementation, but
|
||||
felt that we should re-examine in light of `T?`. In particular, today we do not warn when you create a method signature that assigns
|
||||
`default` to `T` as a default value. This means that it's possible for generic substitution to cause bad method signatures to be
|
||||
created, where a `null` is assigned to a non-nullable reference type. The proposal, then, is to start warning about these cases, in
|
||||
both C# 8 and 9. In C# 8, the workaround is to use `AllowNull` on that parameter, and C# 9 would allow `T?` for that parameter. There
|
||||
was no pushback to this proposal.
|
||||
|
||||
As part of this, we also considered the other locations in this example. For example, we could issue a warning at the callsite of such
|
||||
a method. The proposal would be to expand the warnings on nullable mismatches in parameters to implicit parameters as well. This could
|
||||
end up causing double warning, if both the method and the callsite get a warning here, but it might be able to help users who are using
|
||||
otherwise unannotated methods, or libraries compiled with an older version of the compiler that did not warn here. There is some
|
||||
concern, though, that putting a warning at the callsite is the wrong location. It was the method author that created this invalid
|
||||
signature, and we'd be punishing users with additional warnings. Presumably, if the author allows `null`, they're appropriately
|
||||
handling it, even if the code is still oblivious or in an older version of C#.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The original proposal is approved. We'll continue looking at the callsite proposal as an orthogonal feature and come back when we have
|
||||
a complete proposal to review.
|
||||
|
||||
### Argument state after call for AllowNull parameters
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4102
|
||||
https://github.com/dotnet/roslyn/issues/48605
|
||||
|
||||
Next, we looked at fallout over a previous decision to update the state of variables passed as parameters to a method. This allowed us
|
||||
to bring the behavior of `void M([NotNull] string s)` and `void M(string s)` in line, which caused issues for the BCL (as it meant
|
||||
that any change to add `NotNull` to a parameter would be a good change to make, and they were not interested in updating thousands of
|
||||
methods to do this). However, it caused an unfortunate side effect: `void M([AllowNull] string s)` would have no warnings, and would
|
||||
silently update the parameter state to not null, even though there was absolutely no way `M` could have affected the input argument as
|
||||
it was not passed by ref. We considered 2 arguments for this:
|
||||
|
||||
1. Perhaps the method isn't annotated correctly? The real-world example here is `JsonConvert.WriteJson`, and there is an argument to be
|
||||
made that in C# 9, this parameter would just be declared as `T?`, solving the issue. However, it does feel somewhat obvious that this
|
||||
method shouldn't update the state of the parameter.
|
||||
2. We loosen the "effective" resulting type of the parameter based on the precondition, if the parameter is by-value. `[AllowNull]` would
|
||||
loosen the effective resulting type to `T?`, which would not make any changes to the current state of the argument. We might also do the
|
||||
inverse for `DisallowNull`.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We ran out of time today, but are interested in approach 2 above. We'll come back with a complete proposal for a future LDM and examine it
|
||||
again.
|
|
@ -1,127 +0,0 @@
|
|||
# C# Language Design Meeting for November 11th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [IsRecord in metadata](#isrecord-in-metadata)
|
||||
2. [Triage](#triage)
|
||||
1. [AsyncMethodBuilder](#asyncmethodbuilder)
|
||||
2. [Variable declarations under disjunctive patterns](#variable-declarations-under-disjunctive-patterns)
|
||||
3. [Direct constructor parameters](#direct-constructor-parameters)
|
||||
4. [Always available extension methods](#always-available-extension-methods)
|
||||
5. [Allow `nameof` to access instance members from static contexts](#allow-nameof-to-access-instance-members-from-static-contexts)
|
||||
6. [Add `await` as a dotted postfix operator](#add-await-as-a-dotted-postfix-operator)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "Alright, I'm going to make an analogy to social security here."
|
||||
|
||||
## Discussion
|
||||
|
||||
### IsRecord in metadata
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4121
|
||||
|
||||
We discussed a few different ways to tackle this issue, which relates to customers depending on the presence of the `<Clone>$` method
|
||||
as a way of determining if a type is a `record` or not. First, there are theoretically some ways we could retrofit this method to work
|
||||
as an identifying characteristic, such as by marking `<Clone>$` methods on non-record types, instead of marking the record types in
|
||||
some manner. However, this approach would have to square with `struct` records, which may or may not have that special method. We also need
|
||||
to understand some of the dependent scenarios better: we understand the IDE scenario pretty well, we want to be able to have QuickInfo
|
||||
and metadata-as-source reflect the way the type was declared. However, we don't have an understanding of the EF scenario, and what it
|
||||
would want to do for, say, a non-record class that inherits from a record type. Finally, we considered time frames, and came to the
|
||||
conclusion that the proposed solution would work fine if we wait until C# next to introduce it, and does not require being rushed out the
|
||||
door to be retconned into C# 9: the proposed solution is backwards compatible, as long as it is introduced at the same time as
|
||||
class/record cross inheritance.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Into the Working Set, we'll consider this issue in conjunction with class/record cross-inheritance.
|
||||
|
||||
### Triage
|
||||
|
||||
#### AsyncMethodBuilder
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/1407
|
||||
|
||||
We generally like this proposal, as it solves a real need in the framework while creating a generalized feature that can be plugged into
|
||||
more libraries. We did have a couple of questions come up:
|
||||
1. Should we allow this on just the method, or also the type/module level? This seems to be similar to `SkipLocalsInit`, and could be
|
||||
tedious to rep-specify everywhere.
|
||||
2. Can this solve `ConfigureAwait`? We don't think so: this controls the method builder, not the meaning of `await`s inside the method,
|
||||
so while it could potentially change whether a method call returns a task that synchronizes to the thread context by default, it could
|
||||
only do that for methods defined in your assembly, which would just lead to confusing behavior.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Working Set, we'll work through the proposal in a full LDM session soon.
|
||||
|
||||
#### Variable declarations under disjunctive patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4018
|
||||
|
||||
We like this proposal. There are a couple of open issues/questions that need to be addressed:
|
||||
1. We need a rule that says when you are allowed to redeclare existing variables. It needs to cover multiple switch case labels, while
|
||||
also not permitting things declared outside the switch label to be redeclared.
|
||||
2. How identical do the types need to be? Are nullability differences permitted? ie, are `(object?, object)` and `(object, object?)` the
|
||||
same for the purposes of this feature? It seems like they may have to be.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Working Set. We'll take some time and consider these questions, and we should also consider alternatives at the same time,
|
||||
such as an `into` pattern that would allow a previously-declared variable to be assigned in a pattern, including ones declared outside a
|
||||
pattern.
|
||||
|
||||
#### Direct constructor parameters
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4024
|
||||
|
||||
We discussed this feature during our last look at primary constructors, and our conclusion is that we need to explore the space more fully
|
||||
with both features in mind. There are concerns about abstraction leaks, particularly with property casing.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the Working Set, to be considered in conjunction with primary constructors.
|
||||
|
||||
#### Always available extension methods
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4029
|
||||
|
||||
There was some strong negative reaction to this proposal. However, presented another way it's more interesting: users who use `var` need
|
||||
to include `using`s they otherwise do not need in order to access these types of extension methods, whereas users who do not use `var`
|
||||
will already have the relevant `using` in scope, and will thus see these extension methods. These types of methods are also often ways of
|
||||
working around various C# limitations, such as lack of specialization, and would naturally be defined on the type itself if it was possible.
|
||||
|
||||
We are concerned with doing anything in this space with extension everything/roles/type classes on the horizon, as we don't want to change
|
||||
extension methods in a way that we'd regret with those features.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the backlog. We'll consider this in conjunction with extension everything.
|
||||
|
||||
#### Allow `nameof` to access instance members from static contexts
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4037
|
||||
|
||||
There is some feeling that this is basically just a bug in the spec (or is just an area where it's not super clear, and it's a bug in the
|
||||
implementation). We do think this is generally good: yes, the scenario could just use `string.Length`, but that is not really what the user
|
||||
intended. They wanted the `Length` property on `P`, and if `P` changes to a different type that no longer has `Length`, there should be an
|
||||
error there. Without this, the cliff that `nameof` tries to solve is just moved further, not removed.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into Any Time. We'd accept a community contribution here: it needs to only permit exactly this scenario, not allow any new types of
|
||||
expressions in `nameof`.
|
||||
|
||||
#### Add `await` as a dotted postfix operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4076
|
||||
|
||||
The LDT has very mixed reactions on this. While we are sympathetic to the desire to make awaits more chainable, and the `.` can be viewed
|
||||
as the pipeline operator of the OO world, we don't think this solves enough to make it worth it. Chainability of `await` expressions
|
||||
isn't the largest issue on our minds with `async` code today: that honor goes to `ConfigureAwait`, which this does not solve. We could go
|
||||
a step further with this form by making it a general function that would allow `true`/`false` parameters to control the thread context
|
||||
behavior, but given our mixed reaction to the syntax form as a whole we're not optimistic about the approach. A more general approach that
|
||||
simplified chaining generally for prefix operators would be more interesting.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected. We do like the space of improving `await`, but we don't think this is the way.
|
|
@ -1,81 +0,0 @@
|
|||
# C# Language Design Meeting for November 16th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Ternary comparison operator](#ternary-comparison-operator)
|
||||
2. [Nominal and collection deconstruction](#nominal-and-collection-deconstruction)
|
||||
3. [IgnoreAccessChecksToAttribute](#ignoreaccesscheckstoattribute)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "If it turns out to be really hard, give it to a smarter compiler dev"
|
||||
- "The name is not good: It should be the BreakTermsOfServiceAttribute"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Ternary comparison operator
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4108
|
||||
|
||||
This proposal centers around add a bit of syntax sugar to simply binary comparisons, where a user might want to compare 3 objects
|
||||
for ascending or descending order. Today, the user would have to write `a < b && b < c`, but with this proposal they would just
|
||||
write `a < b < c`. In order to deal with the potential ambiguities, we'd have to first attempt to bind these scenarios as we would
|
||||
today, and if that fails then attempt to bind them as this new "relational chaining" form. This feature would need to have a very
|
||||
specific pattern: if we were to allow `a < b > c`, for example, that could be syntactically ambiguous with a generic, and would need
|
||||
to keep binding to that as it would today. We therefore are only interested in strictly-ordered comparisons: all comparisons in a
|
||||
chain should be less-than/less-than-or-equal, or greater-than/greater-than-or-equal, without mixing between the 2 orders. We are
|
||||
also worried about the compile-time cost of double-binding here, particularly since the most-likely binding will have to be done second,
|
||||
in order to preserve backwards compatability. We also considered allowing more than 3 objects in such a chain: we like the idea, but
|
||||
it will require some spec work as it does not just fall out of the current specification.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Triaged into Any Time. This needs some specification work to allow the 4 or more operators, which would likely be similar in form to
|
||||
the null-conditional operator. Additionally, any implementation will have to take steps to address potential perf issues and demonstrate
|
||||
that it does not adversely affect compilation perf on real codebases.
|
||||
|
||||
### Nominal and collection deconstruction
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4082
|
||||
|
||||
This feature provides unity between patterns and deconstruction assignment. Today, we have tuple deconstruction assignment, and tuple
|
||||
patterns. They evolved in the opposite direction: we started with tuple deconstruction assignment, then added general patterns to the
|
||||
language. We now consider adding nominal deconstruction assignment, to complete the symmetry between the feature sets.
|
||||
|
||||
One thing we want to be careful of here is to not go to far down the path of replicating patterns in assignment. A pattern in an `is`
|
||||
or `switch` forces the user to deal with the case that the pattern did not match, which is not present here. For nominal deconstruction,
|
||||
we can leverage nullable reference types: the user will get a warning if they attempt to deconstruct an element that could be null. For
|
||||
list patterns, though, there is no similar level of warning, and we want to be careful of creating a pit of failure that will result in
|
||||
exceptions at runtime. We are also concerned about some of the aspects of allowing names to be given to outer structures, such as allowing
|
||||
`var { Range: { Column: column } range } = GetRange();`. This could mix badly with allowing existing variable reuse: in patterns today,
|
||||
the `{ ... } identifier` syntax always introduces a new variable, which we think would end up being confusing. We very wary of allowing
|
||||
patterns to match into existing variables because it would introduce side-effects to patterns, which is very concerning. Finally, given
|
||||
that we haven't yet designed regular list patterns, we think we should hold off on list deconstruction assignment until those are complete,
|
||||
at which point we should have a discussion around whether we should have them at all.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Nominal deconstruction assignment is accepted into the working set. Let's split list deconstruction assignment into a separate issue, which
|
||||
will be followed up on after list patterns are designed. Open questions exist on whether we should allow names on patterns themselves.
|
||||
|
||||
### IgnoreAccessChecksToAttribute
|
||||
|
||||
We had a very spirited discussion around this attribute, which is essentially the inverse of `InternalsVisibleToAttribute`. Where IVT
|
||||
allows an author to grant access to a specific other dll, this allows a specific other dll to grant themselves access to an author. There
|
||||
are many challenges around this scheme that fundamentally affect the entire ecosystem, and those discussions need to happen at a .NET
|
||||
ecosystem level, rather than at a language level, even though most of the implementation work will fall on the compiler. Ref assemblies,
|
||||
for example, do not have internal members today. There also needs to be discussions on how we would enforce the "use at your own risk"
|
||||
aspect of this feature. We can say that all we want, but at the end of the day if VS were to take a dependency on an internal Roslyn
|
||||
API that we need to change, it could block shipping until either Roslyn readded the API or the dependency was removed. Given our
|
||||
experiences with `InternalsVisibleToAttribute` already, we're not certain that this burden of risk will be correctly shouldered by the
|
||||
ones actually taking on the risk.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Tabled for now. Discussion needs to happen at a higher level.
|
||||
|
||||
## Working Set Themes
|
||||
|
||||
With our discussions today, we have finished working through our current triage backlog! We've collected the various issues and themes
|
||||
in our working set and created a meta-issue to track them all: https://github.com/dotnet/csharplang/issues/4144. We've locked the issue
|
||||
to ensure that it stays a clean space. For discussion on a particular topic, please see the topic issue, or create a new discussion.
|
|
@ -1,75 +0,0 @@
|
|||
# C# Language Design Meeting for December 2nd, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Partner scenarios in roles and extensions](#partner-scenarios-in-roles-and-extensions)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "Have you noticed how similar what you just said is to function pointers?"
|
||||
- "This is a modern version of COM aggregation." "But in a good way."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Partner scenarios in roles and extensions
|
||||
|
||||
Today, we heard from partner teams on the Azure SDK and on ASP.NET, talking about friction they currently encounter
|
||||
with some scenarios that might be addressable through roles, extension everything, or potentially some other solution.
|
||||
|
||||
#### ASP.NET Feature Collections
|
||||
|
||||
ASP.NET models contexts through an aggregation system that allows different services to be composed onto a single `HttpContext`.
|
||||
For example, adding TLS to a session involves creating a TLS connection, wrapping the existing connection, and adding it
|
||||
as an available feature to the context. This underlying connection could be one of several types connections: it could be
|
||||
routed through a socket, or it could be in-process, or any of a number of other connection mechanisms, each with its own set
|
||||
of properties. It is possible to retrieve each of these sets of features from a `Get` method on the context, but this is
|
||||
cumbersome and not generally extensible: for their users, it would be nice to be able to expose a view over a context or
|
||||
connection that exposed the underlying properties.
|
||||
|
||||
This scenario seems like a clear-cut use case for roles and extension-everything as we last discussed them. A role could be
|
||||
used to expose a grouping of properties on an upper layer from a lower layer. In fact, the ASP.NET architecture was designed
|
||||
with the eventual intention of using extension properties to remove a number of extension methods that they have in place
|
||||
today to expose these underlying properties from a decorated type. Of the 3 scenarios we discussed today, this seems the
|
||||
most obviously-addressed by the existing proposal.
|
||||
|
||||
#### Azure SDKs
|
||||
|
||||
The Azure SDK scenario presents a more interesting challenge. Feature collections were designed with C# in mind, meaning
|
||||
that both types and type names were thoughtfully designed when creating the API. The Azure SDK (and by extension many
|
||||
web APIs), by comparison, are designed in a web-first manner. In this context, property _names_ are important, and general
|
||||
structures of an API are important, but names of these structures are _not_ important. These APIs are often described and
|
||||
generated using Swagger, which uses JSON to describe the structure of a response. JSON structures can be strongly typed,
|
||||
of course: the structure itself is the type. But the nested properties of a JSON object, which can be nested objects
|
||||
themselves, are described entirely in a structural manner, not in a nominal manner as we do in C#. Here, C#'s paradigms
|
||||
break down, and the SDK teams run into trouble when creating a C# API to wrap this structure. All of these nested structures
|
||||
need to be named, so that C# can talk about them. This leads to an explosion of types, which can be made even more difficult
|
||||
when you consider identical structures developed by different teams (perhaps even different companies). By necessity, each
|
||||
team will need to create their own "named" data structure to give C# an ability to talk about the object, but these names
|
||||
are really meaningless. The JSON didn't have this name, and the structures cannot unify. There are also scenarios with
|
||||
very similar objects (perhaps one object has an extra field that the former does not have). This necessitates an entirely
|
||||
new object to be created, and users often end up needing to write boilerplate methods that just translate objects from
|
||||
representation A to B, changing nothing about the data other than making sure the type's name lines up.
|
||||
|
||||
This set of scenarios is not addressed by roles and extension methods, as they currently stand. We theorized that teams
|
||||
might be able to a combination of `dynamic` and a custom completion provider/analyzer, to give users help in writing and
|
||||
validating code that is, in essence, a set of dynamic calls to nested properties of unnamed types that does have a structure,
|
||||
but this is a complicated solution to the scenario that is likely not generally-leverageable. There are more IDEs than just
|
||||
VS, and more IDE scenarios than just completion: what would go-to-def do on these, or quick info on hover?
|
||||
|
||||
#### Data Processing
|
||||
|
||||
Finally, we took a look at a small proof-of-concept library exploring what replicating parts of the popular Python library
|
||||
Pandas could be like in C#, with stronger typing around the data generated from a given input. This scenario is very
|
||||
reminiscent of F# type providers, allowing users to simply dot into a data structure. However, it suffers from the same
|
||||
set of issues that affect the Azure SDK scenarios above. In order to talk about nested data structures, they have to be
|
||||
named. And while the Azure examples entirely focus on the types of structures representable in JSON, Pandas is far more
|
||||
flexible. Additionally, Pandas allows you create new objects as they flow through a pipeline, adding or removing properties
|
||||
as they are manipulated.
|
||||
|
||||
Looking at these last two examples, it seems that there are some scenarios not served well by C# today, involving JSON or
|
||||
other structured but unnamed data. These scenarios care deeply about the names of properties, and the structure attached to
|
||||
each property name, but the domains these scenarios interact with do not care about naming these structures. Further, adding
|
||||
names to these structures in C# can be harmful, because it locks out scenarios that can be accomplished in the original
|
||||
domain and forces a naming structure where none was intended, which can result in confusing or badly-named types that can
|
||||
then never be changed because of backwards compatibility concerns. As we continue to evolve the roles and extension
|
||||
everything proposals, we should look at these scenarios and see if there are ways to improve the experience for them.
|
|
@ -1,30 +0,0 @@
|
|||
# C# Language Design Meeting for December 7th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Required Properties](#required-properties)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "I also can't see anyone's video, so raise your hand [in Teams] if you're not here."
|
||||
- "If you can't solve required properties, you're not making a time machine."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Required Properties
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4209
|
||||
|
||||
Today, we took a look at the next revision of the required properties proposal, after a few months of design work from a smaller
|
||||
team to flesh out the design. We had a small questions coming out of the meeting:
|
||||
|
||||
* Could assignments in the nominal parameter list always imply `base.`? It would make it easier for automatically considering
|
||||
hidden properties being initialized.
|
||||
* We could make it more user friendly by possibly adding warning when a property that is required by the constructor is definitely
|
||||
assigned in the constructor?
|
||||
* There's still some debate as to this should only be a source-breaking change.
|
||||
* Is `init` the right word? Maybe `requires` would be better?
|
||||
|
||||
More generally, the reaction to this in the LDM was mixed. While we believe that this is the best proposal we've seen to date, it's
|
||||
very complicated and introduces a bunch of new concepts. We may need to start looking at simplifying scenarios and seeing whether that
|
||||
allows us to cut this proposal down a bit.
|
|
@ -1,94 +0,0 @@
|
|||
# C# Language Design Meeting for December 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
- [List Patterns](#list-patterns)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "My Monday is your Friday... you're going to get unadulterated truth here"
|
||||
|
||||
## Discussion
|
||||
|
||||
### List Patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/3245
|
||||
|
||||
Today, we took our first in-depth look at list patterns, which will be the next big turn of the crank in generalized pattern
|
||||
support in C#. The intent of this type of pattern is to allow matching on arbitrary collection types, matching against both
|
||||
the length and the content of the collection in a single, simple-to-understand fashion, much like our other pattern forms enable
|
||||
for positional and nominal content. To bring context to the discussion around our general goals and principles for pattern
|
||||
matching in C#, we again brought up the table from discussion [3107](https://github.com/dotnet/csharplang/discussions/3107):
|
||||
|
||||
| Type | Declaration | Creation | Decomposition | Dispatch (pattern) |
|
||||
|-|:-:|:-:|:-:|:-:|
|
||||
|`int`|`int x`|`3`|`int x`|`int x` or `3`
|
||||
|class with mutable fields|`class Point { public int X, Y; }`|`new Point { X = 3, Y = 4 }`|`p.X`|`Point { X: int x, Y: int y }` or `Point { X: 3, Y: 4 }`
|
||||
|anonymous type|-|`new { X = 3, Y = 4 }`|`p.X`|`{ X: int x, Y: int y }` or `{ X: 3, Y: 4 }`
|
||||
|record class|`class Point(int X, int Y);` (proposed)|`Point(3, 4)` (proposed)|`(int x, int y) = p`|`Point(int x, int y)` or `Point(3, 4)`
|
||||
|tuple|`(int x, int y)`|`(3, 4)`|`(int x, int y) = p`|`(int x, int y)` or `(3, 4)`
|
||||
|List|`List<int>`|`new List<int> { 3, 4 }`| ? | **List patterns fit in here**
|
||||
|Dictionary|`Dictionary<K,V>`|`new Dictionary<K,V> { { K, V } }`| ? | ?
|
||||
|
||||
While we are not looking at list decomposition with this proposal, we should keep it in mind, as we will want whatever form
|
||||
we use for pattern dispatch to be used for decomposition as well. In this table, we can see a correspondence between the different
|
||||
syntactic forms of creation and destructuring: object initializers correspond with recursive object patterns, tuple literals
|
||||
correspond with tuple patterns, etc. By this principle, our initial instinct is to make the collection pattern correspond with
|
||||
the collection initializer syntax, which uses curly braces. However, this runs into an immediate issue: `{ }` is already a
|
||||
valid pattern today, the empty object pattern. This means it cannot serve as the empty collection pattern, as that pattern
|
||||
must specifically check that the collection is actually empty. The current proposal instead takes the approach of using square
|
||||
brackets `[]` to represent a collection pattern, instead of using the curlies. We're pretty divided on this approach: C# has
|
||||
not used square brackets to represent a list or array in the past. Even C# 1.0 used the curly brackets for array initializers,
|
||||
reserving the brackets for array size or index access. This would make the proposed syntax a really big break with C# tradition.
|
||||
We could "retcon" this by enabling new types of collection literals using square brackets, but that's an issue that LDM has
|
||||
not intensely looked at beyond previously rejecting https://github.com/dotnet/csharplang/issues/414 and related issues. After
|
||||
some discussion, we've come to the realization that the empty collection (ie, the base case for recursive algorithms) is the
|
||||
most important pattern to design for, and the rest of the syntax falls out from that design. We've come up with a few different
|
||||
syntactic proposals:
|
||||
|
||||
1. The existing proposal as is. Notably, this pattern form is _not_ part of a recursive pattern, and that means that you can't
|
||||
specify a pattern like this: `int[] [1, 2, 3]`. Indeed, such a pattern is potentially ambiguous, as `int[] []` already means
|
||||
a type test against a jagged `int` array today. Instead, such a pattern would have to be expressed as `int[] and []`. The
|
||||
first part narrows the type to `int[]`, and the second part specifies that the array must be empty. We're not huge fans of needing
|
||||
the `and` combinator for a base case (when the input type to the pattern is not narrowed enough to use a collection pattern)
|
||||
given that one is not needed for tuple deconstruction patterns or property patterns, but it is elegant in its simplicity.
|
||||
2. A similar version to 1, except that it allows nesting the square brackets inside the curly braces of the property pattern.
|
||||
This would allow `int[] { [1, 2, 3] }` for the case where you need to both narrow the input type and test the array content.
|
||||
There are some concerns with this syntax: we've also envisioned a dictionary pattern, that would match content using a form
|
||||
like this: `{ [1]: { /* some nested pattern */ } }`. This would mean that the colon at the end of the brackets would determine
|
||||
whether the contents of the brackets are used as arguments to an indexer or the patterns the collection is being tested against.
|
||||
3. Using curlies to match the list contents. There are a couple of sub proposals in this section, separated by the way they
|
||||
enable testing for the empty collection case. They share the content tests, which look like `{ 1, 2, 3 }`.
|
||||
1. No empty case. Instead, use a property pattern on `Length` or `Count` to check for the empty case. This has issues
|
||||
with our previously-desired support for `IEnumerable` and general `foreach`-able type support, as they do not have any
|
||||
such property to check.
|
||||
2. Empty case represented by a single comma: `{,}` would represent an array with `Length == 0`. This was suggested, but
|
||||
no one argued in favor.
|
||||
3. Square brackets for `Length` tests. This proposal would look something like this: `int[] [0]`. The interesting angle
|
||||
with this version is that it allows for succinct length tests that could be composed of patterns itself. For example, the
|
||||
BCL has some cases where they need to check to see whether an array has some content and Length between two cases, and that
|
||||
could be expressed as `[>= 0 and < 256] { 1, 2, .. }`. This would also allow a general length check to be expressed for
|
||||
`foreach`-able types, though there are some concerns that it would become a perf pitfall if enumerating the entire sequence
|
||||
was necessary to check a non-zero length. The length or count of the collection could also be extracted with a declaration
|
||||
pattern, which could turn into a nice shorthand for not having to know whether this collection uses `Length` or `Count`,
|
||||
something we didn't standardize and now can't. How this version combines with other property tests on the same object would
|
||||
still need to be discussed: could you do `MyType { Property: a } [10] { 1, 2, 3, .. }`, for example, or would the property and
|
||||
collection patterns need to be combined with an `and`?
|
||||
4. Add a new combinator keyword to make the transition to a collection pattern explicit. This is similar to how VB uses
|
||||
`From` to indicate collection initializers. Such a pattern might look something like `int[] with { }` for the empty case.
|
||||
(`with` was the word we spitballed here, but likely wouldn't end up being the final word for confusion with `with` expressions).
|
||||
|
||||
We came to no solid conclusions on the syntax topic today, as we were mostly generating ideas and need some time to mull over the
|
||||
various forms. We'll come back to this at a later date.
|
||||
|
||||
We also took a brief look at the slice pattern and whether it could be extended to `foreach`-able types. A trailing `..` in a
|
||||
`foreach` match would be easy to implement and not have any hidden costs, as it would just skip a check to `MoveNext()` after
|
||||
the leading bits of the pattern match. However, a leading `..` would be much more concerning. Depending on implementation
|
||||
strategy, we'd have emit a much larger state machine or keep track of a potentially large number of previous values as we
|
||||
iterate the enumerable, so that when we get to the end we can ensure that the previous slots matched correctly. We're not
|
||||
sure if this difference will be obvious enough to users, and will need to think more about whether we should enable the trailing
|
||||
slice, or enable both slice patterns and let the codegen be what it will. In all likelihood, if the user needs this pattern
|
||||
they're going to code it by hand if they can't do it with a pattern, and we can make it less likely to introduce a bug for it
|
||||
if we generate the states programmatically instead of the user doing it by hand.
|
||||
|
||||
Again, we came to no solid conclusions here, as we spent most of our time on the syntax aspects.
|
|
@ -1,87 +0,0 @@
|
|||
# C# Language Design Meeting for December 14th, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [List patterns](#list-patterns)
|
||||
2. [Definite assignment changes](#definite-assignment-changes)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "They are syntax forms you little devil. No amount pedanticism is too much in C#."
|
||||
|
||||
## Discussion
|
||||
|
||||
### List patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/3245
|
||||
|
||||
Coming out of [Monday's meeting](LDM-2020-12-14.md), we had a few different competing proposals for syntax. As a quick recap:
|
||||
|
||||
1. The original proposal as is.
|
||||
2. Put the brackets from 1 inside the braces on the top level.
|
||||
3. Use braces for the list pattern, with the empty case being:
|
||||
1. No empty case.
|
||||
2. `{,}`
|
||||
3. Add a `[pattern]` form that allows testing and potentially extracting the length of a collection.
|
||||
4. Add a new combinator to make the braces explicitly a list pattern, which would allow `{ }` to be the base case.
|
||||
|
||||
After the notes were published, we took the list and had an email discussion to narrow in on the specifics of each of these cases.
|
||||
Cases 2, 3.i, 3.ii, and 3.iv were not defended in this email chain, and coming into today's meeting there were 4 different main syntax
|
||||
proposals, the final 3 being variations of 3.iii from the original list (in psuedo-antlr):
|
||||
|
||||
1. The original proposal. This introduces a new `pattern`, which uses `'[' (pattern (',' pattern)* ']')` as the syntax of that
|
||||
new pattern. This cannot be expressed as a top-level concept in a `positional_pattern` or a `property_pattern` because the braces
|
||||
can be ambiguous with the `type` component of these patterns.
|
||||
2. `type? ('(' subpatterns? ')')? ('[' pattern ']')? ('{' property_subpatterns_or_list_element_patterns '}')?`.
|
||||
This form modifies the `positional_pattern` syntax introduced in C# 8 to add a length pattern section, defined by the middle
|
||||
`[pattern]` section, and modifies the final braces to contain either a set of positional subpatterns, or a set of list element
|
||||
patterns, but not both. To test both property elements and list elements, a conjunctive pattern needs to be used.
|
||||
3. `type? ('(' subpatterns? ')')? ('[' pattern ']')? ('{' property_subpatterns '}')? ('{' list_subpatterns '}')?`.
|
||||
This is very similar to 2, except that it allows both property and list subpatterns at the same time.
|
||||
4. `type? ('(' subpatterns? ')')? ('[' pattern ']')? ('{' property_subpatterns_and_list_element_patterns '}')?`.
|
||||
This is very similar to 2, except that it allows both property subpatterns and list subpatterns in the same set of braces. Consider
|
||||
subproposals of this version to require properties first, list elements first, or no ordering requirements.
|
||||
|
||||
The very important goal for the language team here is to follow the correspondence principle. That means that if you construct using one
|
||||
syntax construct, you should use the same construct to deconstruct. For collection types, this means that we strongly want to prefer
|
||||
using curly braces as the deconstruction syntax, rather than square brackets, because collection initializers use the braces. It is
|
||||
possible that at some point in the future, we could add a collection literal syntax that uses square brackets, but there is strong
|
||||
history in C# to avoid using the brackets in this fashion. Up to this point, the brackets have always contained indexes or lengths
|
||||
in the language, and lists of things to initialize have always been inside braces. Changing that at this point, even if we later seek
|
||||
to add conformance by introducing a new collection literal, would be asking C# users to unlearn a concept that has been unchanged
|
||||
since C# 1.0, which is very concerning to us. Given this desire, option 1 deviates too much from existing C#, and we will instead
|
||||
focus on one of the latter options.
|
||||
|
||||
Of these latter options, option 2 can be viewed as a strict subset of both 3 and 4, as either will allow using conjunctive patterns
|
||||
to separate out the list and property patterns if users feel that the combination is unreadable. Additionally, we again turn to the
|
||||
correspondence principle: today, you cannot combine both collection initializers and object initializers. By the correspondence
|
||||
principle, then, you should not be able to combine them in the same pattern during deconstruction. We're not necessarily opposed to
|
||||
allowing collection and object initializers to be combined in the future, but that is out of scope for the collection pattern changes.
|
||||
|
||||
Finally, in discussions Monday and over email, we also took a brief look at indexer patterns as possible `property_subpatterns`. These
|
||||
would look something like this: `{ [1]: pattern, [2..^4]: var slice }`. This form seems like a good next step after list patterns to
|
||||
allow deconstructioning objects with indexers. These indexer arguments could allow non-integer constants as well as multiple arguments,
|
||||
giving a deconstruction mechanism for dictionaries that corresponds to object initializers in this area.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll move forward with the general syntax proposed in option 2, with a length subpattern and allowing either property subpatterns or
|
||||
list subpatterns in the same "recursive pattern" section. We still need to translate the psuedo-spec above into a formal specification.
|
||||
We also did not address the open questions around whether the length pattern should be applicable to types of `IEnumerable`, and if so
|
||||
whether `[0]` is the only allowed pattern or if any pattern is allowable.
|
||||
|
||||
### Definite assignment changes
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4240
|
||||
|
||||
This is an area of longstanding pain for C# users: any time conditional evaluation and comparison to constants mix, definite assignment
|
||||
cannot figure out what is going on and variables that the user can see are obviously assigned are not considered assigned. We're highly
|
||||
in support of this idea in general, as everyone has run into this at some point or another in their C# careers. The definite assignment
|
||||
rules are written in a very syntax-driven form, and thus this proposal is written in a very syntax-driven form to update the relevant
|
||||
constructs. Despite that, we do wonder whether we can make these rules more holistic and systematic, such that we don't need to make
|
||||
them syntax-specific like they need to be today. We're also less enthused about the conditional expression version. If it fell out of
|
||||
more general rules it would be nice, but it's not highly important like the null conditional and coalescing changes seem to be.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
The general idea is approved. We'll work to see if we can generalize the rules a bit, and submit a proposal for review on github.
|
|
@ -1,452 +0,0 @@
|
|||
# C# Language Design Notes for 2020
|
||||
|
||||
Overview of meetings and agendas for 2020
|
||||
|
||||
## Dec 16, 2020
|
||||
|
||||
[C# Language Design Notes for December 16th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-16.md)
|
||||
|
||||
- List patterns
|
||||
- Definite assignment changes
|
||||
|
||||
## Dec 14, 2020
|
||||
|
||||
[C# Language Design Notes for December 14th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-14.md)
|
||||
|
||||
- List patterns
|
||||
|
||||
## Dec 7, 2020
|
||||
|
||||
[C# Language Design Notes for December 7th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-07.md)
|
||||
|
||||
- Required Properties
|
||||
|
||||
## Dec 2, 2020
|
||||
|
||||
[C# Language Design Notes for December 2nd, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-12-02.md)
|
||||
|
||||
- Partner scenarios in roles and extensions
|
||||
|
||||
## Nov 16, 2020
|
||||
|
||||
[C# Language Design Notes for November 16th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-11-16.md)
|
||||
|
||||
- Triage
|
||||
|
||||
## Nov 11, 2020
|
||||
|
||||
[C# Language Design Notes for November 11th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-11-11.md)
|
||||
|
||||
- IsRecord in metadata
|
||||
- Triage
|
||||
|
||||
## Nov 4, 2020
|
||||
|
||||
[C# Language Design Notes for November 4th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-11-04.md)
|
||||
|
||||
- Nullable parameter defaults
|
||||
- Argument state after call for AllowNull parameters
|
||||
|
||||
## Oct 26, 2020
|
||||
|
||||
[C# Language Design Notes for October 26st, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-26.md)
|
||||
|
||||
- Pointer types in records
|
||||
- Triage
|
||||
|
||||
## Oct 21, 2020
|
||||
|
||||
[C# Language Design Notes for October 21st, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-21.md)
|
||||
|
||||
- Primary Constructors
|
||||
- Direct Parameter Constructors
|
||||
|
||||
## Oct 14, 2020
|
||||
|
||||
[C# Language Design Notes for October 14th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-14.md)
|
||||
|
||||
- Triage
|
||||
- Milestone Simplification
|
||||
|
||||
## Oct 12, 2020
|
||||
|
||||
[C# Language Design Notes for October 12th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-12.md)
|
||||
|
||||
- General improvements to the `struct` experience (continued)
|
||||
|
||||
## Oct 7, 2020
|
||||
|
||||
[C# Language Design Notes for October 7th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-07.md)
|
||||
|
||||
- `record struct` syntax
|
||||
- `data` members redux
|
||||
- `ReadOnlySpan<char>` patterns
|
||||
|
||||
## Oct 5, 2020
|
||||
|
||||
[C# Language Design Notes for October 5th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-05.md)
|
||||
|
||||
- `record struct` primary constructor defaults
|
||||
- Changing the member type of a primary constructor parameter
|
||||
- `data` members
|
||||
|
||||
## Sep 30, 2020
|
||||
|
||||
[C# Language Design Notes for September 30th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-30.md)
|
||||
|
||||
- `record structs`
|
||||
- `struct` equality
|
||||
- `with` expressions
|
||||
- Primary constructors and `data` properties
|
||||
|
||||
## Sep 28, 2020
|
||||
|
||||
[C# Language Design Notes for September 28th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-28.md)
|
||||
|
||||
- Warning on `double.NaN`
|
||||
- Triage
|
||||
|
||||
## Sep 23, 2020
|
||||
|
||||
[C# Language Design Notes for September 23rd, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-23.md)
|
||||
|
||||
- General improvements to the `struct` experience
|
||||
|
||||
## Sep 16, 2020
|
||||
|
||||
[C# Language Design Notes for September 16th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-16.md)
|
||||
|
||||
- Required Properties
|
||||
- Triage
|
||||
|
||||
## Sep 14, 2020
|
||||
|
||||
[C# Language Design Notes for September 14th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-14.md)
|
||||
|
||||
- Partial method signature matching
|
||||
- Null-conditional handling of the nullable suppression operator
|
||||
- Annotating IEnumerable.Cast
|
||||
- Nullability warnings in user-written record code
|
||||
- Tuple deconstruction mixed assignment and declaration
|
||||
|
||||
## Sep 9, 2020
|
||||
|
||||
[C# Language Design Notes for September 9th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-09-09.md)
|
||||
|
||||
- Triage issues still in C# 9.0 candidate
|
||||
- Triage issues in C# 10.0 candidate
|
||||
|
||||
## Aug 24, 2020
|
||||
|
||||
[C# Language Design Notes for August 24th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-08-24.md)
|
||||
|
||||
- Warnings on types named `record`
|
||||
- `base` calls on parameterless `record`s
|
||||
- Omitting unnecessary synthesized `record` members
|
||||
- [`record` `ToString` behavior review](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md#printing-members-printmembers-and-tostring-methods)
|
||||
- Behavior of trailing commas
|
||||
- Handling stack overflows
|
||||
- Should we omit the implementation of `ToString` on `abstract` records
|
||||
- Should we call `ToString` prior to `StringBuilder.Append` on value types
|
||||
- Should we try and avoid the double-space in an empty record
|
||||
- Should we try and make the typename header print more economic
|
||||
- Reference equality short circuiting
|
||||
|
||||
## Jul 27, 2020
|
||||
|
||||
[C# Language Design Notes for July 27th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-27.md)
|
||||
|
||||
- [Improved nullable analysis in constructors](https://github.com/RikkiGibson/csharplang/blob/nullable-ctor/proposals/nullable-constructor-analysis.md) (Rikki)
|
||||
- [Equality operators (`==` and `!=`) in records](https://github.com/dotnet/csharplang/issues/3707#issuecomment-661800278) (Fred)
|
||||
- `.ToString()` or `GetDebuggerDisplay()` on records? (Julien)
|
||||
- Restore W-warning to `T t = default;` for generic `T`, now you can write `T?`? (Julien)
|
||||
|
||||
## Jul 20, 2020
|
||||
|
||||
[C# Language Design Notes for July 20th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-20.md)
|
||||
|
||||
- [struct private fields in definite assignment](https://github.com/dotnet/csharplang/issues/3431) (Neal/Julien)
|
||||
- [Proposal 1](https://github.com/dotnet/roslyn/issues/30194#issuecomment-657858716)
|
||||
- [Proposal 2](https://github.com/dotnet/roslyn/issues/30194#issuecomment-657900257)
|
||||
- Finish [Triage](https://github.com/dotnet/csharplang/issues?q=is%3Aopen+is%3Aissue+label%3A%22Proposal+champion%22+no%3Amilestone)
|
||||
- Records-related features to pick up in the next version of C# (Mads)
|
||||
|
||||
|
||||
## Jul 13, 2020
|
||||
|
||||
[C# Language Design Notes for July 13th, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-13.md)
|
||||
|
||||
- Triage open issues
|
||||
|
||||
## Jul 6, 2020
|
||||
|
||||
[C# Language Design Notes for July 6, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-06.md)
|
||||
|
||||
- [Repeat Attributes in Partial Members](https://github.com/RikkiGibson/csharplang/blob/repeated-attributes/proposals/repeat-attributes.md) (Rikki)
|
||||
- `sealed` on `data` members
|
||||
- [Required properties](https://github.com/dotnet/csharplang/issues/3630) (Fred)
|
||||
|
||||
|
||||
## Jul 1, 2020
|
||||
|
||||
[C# Language Design Notes for July 1, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-07-01.md)
|
||||
|
||||
- [Non-defaultable struct types](https://github.com/dotnet/csharplang/issues/99#issuecomment-601792573) (Sam, Chuck)
|
||||
- Confirm unspeakable `Clone` method and long-term implications (Jared/Julien)
|
||||
|
||||
## Jun 29, 2020
|
||||
|
||||
[C# Language Design Notes for June 29, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-29.md)
|
||||
|
||||
- [Static interface members](https://github.com/Partydonk/partydonk/issues/1) (Miguel, Aaron, Mads, Carol)
|
||||
|
||||
## Jun 24, 2020
|
||||
|
||||
[C# Language Design Notes for June 24, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-24.md)
|
||||
|
||||
- Parameter null checking: finalize syntax
|
||||
- https://github.com/dotnet/csharplang/issues/3275 Variance on static interface members (Aleksey)
|
||||
- [Function pointer question](https://github.com/dotnet/roslyn/issues/39865#issuecomment-647692516) (Fred)
|
||||
|
||||
|
||||
## Jun 22, 2020
|
||||
|
||||
[C# Language Design Notes for June 22, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-22.md)
|
||||
|
||||
1. Data properties
|
||||
|
||||
1. Clarifying what's supported in records for C# 9
|
||||
|
||||
- Structs
|
||||
|
||||
- Inheritance with records and classes
|
||||
|
||||
## Jun 17, 2020
|
||||
|
||||
[C# Language Design Notes for June 17, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-17.md)
|
||||
|
||||
1. Null-suppression & null-conditional operator
|
||||
1. `parameter!` syntax
|
||||
1. `T??`
|
||||
|
||||
## Jun 15, 2020
|
||||
|
||||
[C# Language Design Notes for June 15, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-15.md)
|
||||
|
||||
Record:
|
||||
|
||||
1. `modreq` for init accessors
|
||||
|
||||
1. Initializing `readonly` fields in same type
|
||||
|
||||
1. `init` methods
|
||||
|
||||
1. Equality dispatch
|
||||
|
||||
1. Confirming some previous design decisions
|
||||
|
||||
1. `IEnumerable.Current`
|
||||
|
||||
## Jun 10, 2020
|
||||
|
||||
[C# Language Design Notes for June 10, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-10.md)
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/1711 Roles and extensions
|
||||
|
||||
## Jun 1, 2020
|
||||
|
||||
[C# Language Design Notes for June 1, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-01.md)
|
||||
|
||||
Records:
|
||||
1. Base call syntax
|
||||
2. Synthesizing positional record members and assignments
|
||||
3. Record equality through inheritance
|
||||
|
||||
## May 27, 2020
|
||||
|
||||
[C# Language Design Notes for May 27, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-27.md)
|
||||
|
||||
Record syntax
|
||||
1. Record structs?
|
||||
2. Record syntax/keyword
|
||||
3. Details on property shorthand syntax
|
||||
|
||||
## May 11, 2020
|
||||
|
||||
[C# Language Design Notes for May 11, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-11.md)
|
||||
|
||||
Records
|
||||
|
||||
## May 6, 2020
|
||||
|
||||
[C# Language Design Notes for May 6, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-06.md)
|
||||
|
||||
1. Target-typing ?: when the natural type isn't convertible to the target type.
|
||||
1. Allow `if (x is not string y)` pattern.
|
||||
1. Open issues in extension `GetEnumerator`
|
||||
1. Args in top-level programs
|
||||
|
||||
## May 4, 2020
|
||||
|
||||
[C# Language Design Notes for May 4, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-05-04.md)
|
||||
|
||||
1. Reviewing design review feedback
|
||||
|
||||
## April 27, 2020
|
||||
|
||||
[C# Language Design Notes for April 27, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-27.md)
|
||||
|
||||
Records: positional & primary constructors
|
||||
|
||||
## April 20, 2020
|
||||
|
||||
[C# Language Design Notes for April 20, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-20.md)
|
||||
|
||||
Records: Factories
|
||||
|
||||
## April 15, 2020
|
||||
|
||||
[C# Language Design Notes for April 15, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-15.md)
|
||||
|
||||
1. Non-void and non-private partial methods
|
||||
2. Top-level programs
|
||||
|
||||
## April 13. 2020
|
||||
|
||||
[C# Language Design Notes for April 13, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-13.md)
|
||||
|
||||
1. Roadmap for records
|
||||
2. Init-only properties
|
||||
|
||||
## April 8, 2020
|
||||
|
||||
[C# Language Design Notes for April 8, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-08.md)
|
||||
|
||||
1. `e is dynamic` pure null check
|
||||
2. Target typing `?:`
|
||||
3. Inferred type of an `or` pattern
|
||||
4. Module initializers
|
||||
|
||||
## April 6, 2020
|
||||
|
||||
[C# Language Design Notes for April 6, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-06.md)
|
||||
|
||||
1. Record Monday: Init-only members
|
||||
|
||||
## April 1, 2020
|
||||
|
||||
[C# Language Design Notes for April 1, 2020](https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-01.md)
|
||||
|
||||
1. Function pointer design adjustments
|
||||
|
||||
2. `field` keyword in properties
|
||||
|
||||
## March 30, 2020
|
||||
|
||||
1. Record Monday
|
||||
|
||||
[C# Language Design Notes for March 30, 2020](LDM-2020-03-30.md)
|
||||
|
||||
## March 25, 2020
|
||||
|
||||
[C# Language Design Notes for March 25, 2020](LDM-2020-03-25.md)
|
||||
|
||||
1. Open issues with native int
|
||||
|
||||
2. Open issues with target-typed new
|
||||
|
||||
## March 23, 2020
|
||||
|
||||
[C# Language Design Notes for March 23, 2020](LDM-2020-03-23.md)
|
||||
|
||||
1. Triage
|
||||
2. Builder-based records
|
||||
|
||||
## March 9, 2020
|
||||
|
||||
[C# Language Design Notes for March 9, 2020](LDM-2020-03-09.md)
|
||||
|
||||
1. Simple programs
|
||||
|
||||
2. Records
|
||||
|
||||
## Feb 26, 2020
|
||||
|
||||
[C# Language Design Notes for Feb. 26, 2020](LDM-2020-02-26.md)
|
||||
|
||||
Design Review
|
||||
|
||||
## Feb 24
|
||||
|
||||
[C# Language Design Notes for Feb. 24, 2020](LDM-2020-02-24.md)
|
||||
|
||||
Taking another look at "nominal" records
|
||||
|
||||
## Feb 19
|
||||
|
||||
[C# Language Design Notes for Feb. 19, 2020](LDM-2020-02-19.md)
|
||||
|
||||
State-based value equality
|
||||
|
||||
## Feb 12
|
||||
|
||||
[C# Language Design Notes for Feb. 12, 2020](LDM-2020-02-12.md)
|
||||
|
||||
Records
|
||||
|
||||
## Feb 10
|
||||
|
||||
[C# Language Design Notes for Feb. 10, 2020](LDM-2020-02-10.md)
|
||||
|
||||
Records
|
||||
|
||||
## Feb 5
|
||||
|
||||
[C# Language Design Notes for Feb. 5, 2020](LDM-2020-02-05.md)
|
||||
|
||||
- Nullability of dependent calls (Chuck, Julien)
|
||||
- https://github.com/dotnet/csharplang/issues/3137 Records as individual features (Mads)
|
||||
|
||||
## Feb 3
|
||||
|
||||
[C# Language Design Notes for Feb. 3, 2020](LDM-2020-02-03.md)
|
||||
|
||||
Value Equality
|
||||
|
||||
## Jan 29, 2020
|
||||
|
||||
[C# Language Design Notes for Jan. 29, 2020](LDM-2020-01-29.md)
|
||||
|
||||
Records: "With-ers"
|
||||
|
||||
## Jan 22, 2020
|
||||
|
||||
[C# Language Design Notes for Jan 22, 2020](LDM-2020-01-22.md)
|
||||
|
||||
1. Top-level statements and functions
|
||||
2. Expression Blocks
|
||||
|
||||
## Jan 15, 2020
|
||||
|
||||
[C# Language Design Notes for Jan 15, 2020](LDM-2020-01-15.md)
|
||||
|
||||
Records
|
||||
|
||||
1. "programming with data"
|
||||
1. Decomposing subfeatures of records
|
||||
|
||||
## Jan 8, 2020
|
||||
|
||||
[C# Language Design Notes for Jan 8, 2020](LDM-2020-01-08.md)
|
||||
|
||||
1. Unconstrained type parameter annotation
|
||||
2. Covariant returns
|
||||
|
||||
## Jan 6, 2020
|
||||
|
||||
[C# Language Design Notes for Jan 6, 2020](LDM-2020-01-06.md)
|
||||
|
||||
1. Use attribute info inside method bodies
|
||||
1. Making Task-like types covariant for nullability
|
||||
1. Casting to non-nullable reference type
|
||||
1. Triage
|
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 2 MiB |
|
@ -1,48 +0,0 @@
|
|||
# C# Language Design Meeting for Jan. 5th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [File-scoped namespaces](#file-scoped-namespaces)
|
||||
|
||||
## Quote of the Day:
|
||||
|
||||
- "I see a big tropical void where [redacted's] face was... It's so annoying"
|
||||
- "It's so cold here, it's 70 [F]"
|
||||
|
||||
## Discussion
|
||||
|
||||
### File-scoped namespaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/137
|
||||
|
||||
Today, we looked at some of the details around this feature, specifically what should be supported before or after a top-level
|
||||
namespace declaration. The proposal as written allows only extern aliases, using directives, and global attributes. The proposal
|
||||
also does not allow multiple top-level namespaces: you can only have one in the file, and all following type declarations are
|
||||
considered to be part of that namespace. The debate, therefore, centers on whether we allow multiple of these declarations in a
|
||||
file, what they would mean in that case, and whether we allow a top-level namespace at the same file as top-level statements.
|
||||
|
||||
In many ways, this seems like a style question. Syntactically, regardless of whether allow these concepts to be mixed/duplicated
|
||||
in a single file in the formal grammar, the compiler will have to implement rules for what this means in order to provide a good
|
||||
IDE experience. There is potential value is allowing this to be flexible, as we generally do not take strong stances on syntax
|
||||
formatting guidelines beyond the default rules shipped with Roslyn, and those are very customizable to allow users to decide
|
||||
whether to allow them or not (`var` vs explicit type has 3 major doctrines, for example). By allowing all forms here, we would
|
||||
let users decide what is preferred to them and what is not.
|
||||
|
||||
That being said, however, we have concerns that we even understand what code like this would do:
|
||||
```cs
|
||||
namespace X;
|
||||
class A {}
|
||||
namespace Y;
|
||||
class B {}
|
||||
```
|
||||
For this scenario, some people would expect these types to be `X.A` and `Y.B`, while others would expect them to be `X.A` and
|
||||
`X.Y.B`. We have additional concerns around how this type of code would read in the presence of top-level statements, and
|
||||
whether there would be enough visual contrast between the end of the top-level statements, the namespace, and then types under
|
||||
the namespace, or whether that would be confusing to read. If we restrict the usage now, nothing would stop us from loosening
|
||||
restrictions in a later language version if we discover that we were too restrictive initially, but if we let the genie out of
|
||||
the bottle now, we can never put it back in.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
No conclusion today. We're largely split between these two extremes, allowing everything or allowing nothing. We'll take this
|
||||
back up again soon to finish debate and settle on a conclusion.
|
|
@ -1,65 +0,0 @@
|
|||
# C# Language Design Meeting for Jan. 11th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Required properties simple form](#required-properties-simple-form)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "You didn't want to hear me say um anyway... So, um"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Required properties simple form
|
||||
|
||||
https://github.com/dotnet/csharplang/discussions/4209#discussioncomment-275575
|
||||
|
||||
Today, we took a look at an extension to the existing required properties proposal, that proposed a syntax form
|
||||
that simplifies the base case of the proposal to remove complexity for what we believe is the 80% use case. This
|
||||
form adds a `require` modifier on a property definition. These requirements and then automatically added to the
|
||||
implicit parameterless constructor of a type, if present, and can be added to explicit constructors with
|
||||
`require default`, in the same place as the `init` lists of the last proposal.
|
||||
|
||||
First, we discussed the syntax in the proposal, and potential alternatives. We like the move to put a modifier
|
||||
on properties and fields as it makes implicit constructor scenarios much simpler, but something still feels off
|
||||
about the full-blown form of this syntax, with `require { Property, List }`. We could draw on type parameter
|
||||
constraint clauses, and a rough first attempt looks promising:
|
||||
|
||||
```cs
|
||||
public Person() require FirstName, LastName
|
||||
{
|
||||
}
|
||||
|
||||
public Student() require ID
|
||||
: base()
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
The proposed ability to assign to properties in the require list might be either odd or outright impossible here,
|
||||
depending on potential syntactic ambiguities we haven't thought of yet, but the syntax immediately feels more
|
||||
like C# than the previous curly-brace version.
|
||||
|
||||
Where we spent the bulk of meeting, however, was on how implicit we can make the default parameter list. We
|
||||
had immediate pushback on `require default` even being necessary on constructors: why can't we just infer that
|
||||
for all constructors in a type, and then have a syntax for removing all requirements? There's a feeling that
|
||||
`require default` is just busywork, and the compiler should just infer the defaults from the properties and
|
||||
fields in the type that are marked `require`. Some proposals for the ability to remove all requirements are
|
||||
`require none` and `require default = _`. We also considered a version of the proposal that goes even further,
|
||||
that doesn't allow constructors to `require` additional items: you mark a property or field as required, then
|
||||
remove requirements in the constructor itself. In this model, constructors would be unable to add new requirements,
|
||||
which does remove some potential scenarios, but could simplify the feature significantly. Roughly speaking, the
|
||||
three versions of the proposal can be summarized as follows:
|
||||
|
||||
1. Only implicit constructors get implicit require lists.
|
||||
2. All constructors get implicit require lists, and can add requirements of their own:
|
||||
1. If the constructor calls base in some manner (including implicit calls to the `object` constructor), that
|
||||
list is all the fields and properties in the type that are marked require.
|
||||
2. If the constructor calls another constructor on `this`, then it simply advertises chaining to that
|
||||
constructor, potentially removing some requirements if it takes care of them in its body.
|
||||
3. All constructors get implicit require lists, and cannot add to them. They can only remove them, and there
|
||||
is no `require` syntax. This version will need a new syntax for removing requirements, but that will likely
|
||||
be much simpler than the full `require` clause and need less user education.
|
||||
|
||||
After a read of the room, we're interested in seeing where proposal 3 goes. We'll work on fleshing that out
|
||||
with examples and bring it back to LDM soon.
|
|
@ -1,84 +0,0 @@
|
|||
# C# Language Design Meeting for Jan. 13th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Global usings](#global-usings)
|
||||
2. [File-scoped namespaces](#file-scoped-namespaces)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "You're not yelling at me. You're just wrong."
|
||||
- "All language rules are arbitrary. Some are just more arbitrary than others."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Global usings
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/3428
|
||||
|
||||
Today, we started by looking at a long-standing request in various forms: the ability to create using directives that apply
|
||||
to an entire project. This is of particular importance now as we solidify our work in relation to the .NET 6 themes, especially
|
||||
around both beginner scenarios and general ease-of-use. A well-studied problem in teaching a programming language is avoiding
|
||||
cognitive load, and usings are an ever-present cognitive load in C#, even in simple "Hello, World!" style applications. Either
|
||||
the teacher says "Ignore the `using` thing for now" or they say "Ignore what the `.`s mean for now", with no ability to hold
|
||||
off on introducing them. That's not to say teaching `using` isn't necessary, and necessary early in the teaching process; merely
|
||||
that delaying that introduction from the first second of seeing C# to a day or week into the curriculum can be very helpful in
|
||||
avoiding overloading newcomers and scaring them off.
|
||||
|
||||
While it's an important scenario, beginners aren't the only driving motivation here. .NET 6 is looking at making both beginner
|
||||
and general scenarios better, and there's an argument that this will help general scenarios as well. Large-sized projects such
|
||||
as dotnet/roslyn are the exception, not the rule; most .NET projects are smaller and don't have nearly so many moving and
|
||||
interacting pieces. `using` boilerplate has a bigger impact on these projects, particularly as they tend to use many frameworks
|
||||
and a custom project SDK (such as ASP.NET). That custom SDK, combined with a feature to allow the SDK to specify global usings
|
||||
in some manner, can help ease these scenarios and remove unnecessary lines from most files in such solutions. Larger projects
|
||||
like Roslyn may never use this feature, but Roslyn and projects like it are not the projects that much of our users are actually
|
||||
writing.
|
||||
|
||||
Broadly, there are two possible approaches to this type of feature: CLI flags, specifiable for actual users via the project file,
|
||||
and a syntax form that allows a user to specify a using should apply to all files. We have an existing proposal for the former
|
||||
approach, 3428 (linked above), and some spitball ideas for what the latter could look like (perhaps something like
|
||||
`global using System;`). Both have advantages:
|
||||
|
||||
* If these are specified via command line flags, then there is one place to go looking for them: the project file. A syntax form
|
||||
would be potentially able to be spread out among multiple files. It is would be possible to spread these out across multiple
|
||||
props files if users wanted to, but the types of projects that use these features are likely rarer than the types of projects
|
||||
that use multiple C# files. Tooling could certainly help here, such as creating a new node in the project explorer to list all
|
||||
the global usings for a project, but we do still need to consider cases where code is not viewed in an IDE such as on GitHub.
|
||||
* We have a number of long-standing requests for having global using aliases. While these can be accomplished via the CLI flag
|
||||
proposal, it would be significantly easier and more accessible to users if they had a syntax form of doing so.
|
||||
* A syntax form would allow participation from source generators. We're somewhat split on whether that's a good thing or not.
|
||||
* A syntax form might be a barrier to potential abilities to do things like `dotnet run csfile` in the future: where would the
|
||||
syntax form live? An ethereal temp file, or a hardcoded part of the SDK?
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We seem to have unanimous agreement here that the design space is interesting, and we would like a feature to address the issues
|
||||
discussed above. We're much more split on the potential approaches, however, and need to explore the space more in depth.
|
||||
|
||||
### File-scoped namespaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/137
|
||||
|
||||
We're picking back up on the discussion from [last week](LDM-2021-01-05.md#file-scoped-namespaces), which is around how
|
||||
restrictive we should be in permitted mixing and matching of multiple namespace statements and combination with traditional
|
||||
namespace directives. An important point to acknowledge is that, regardless of what decision we make here, Roslyn is going to
|
||||
have to do something to understand what multiple namespace directives in a file means because it will encounter that code at
|
||||
some point, regardless of whether it's valid or not, and will have to do its level best to make a guess as to what the user
|
||||
meant. There is a big difference between a compiler trying to make as much sense as it can of invalid code and the language
|
||||
having actual rules for the scenario, though. The scenario we're targeting specifically is files with one namespace in them
|
||||
(and most often, one type as well), and these scenarios make up roughly 99.8% of C# syntax files that lived on one LDT-member's
|
||||
computer. This includes the Roslyn codebase, which has several of these types of files specifically for the purposes of
|
||||
testing that we handle the scenario correctly. Measuring an even broader set of millions of C# files on GitHub shows literally
|
||||
99.99% of files have just one namespace in them.
|
||||
|
||||
We also briefly discussed the interaction with top-level statements. On the one hand, we're concerned about the readability of
|
||||
combining these things, and that the namespace statement would be too easily missed. On the other hand, having just finished
|
||||
talking about beginner scenarios, it seems like it might be annoying that beginners couldn't be introduced to the simple form
|
||||
until they start splitting things apart into multiple classes. Users will likely police themselves here if it doesn't read well,
|
||||
and maybe restricting it is just adding an arbitrary restriction.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We allow one and only one file-scoped namespace per file. You cannot combine a file-scoped namespace with a traditional
|
||||
namespace directive in the same file. We did not reach a conclusion on the combination with top-level statements and will
|
||||
pick that up again soon.
|
|
@ -1,117 +0,0 @@
|
|||
# C# Language Design Meeting for Jan. 27th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Init-only access on conversion on `this`](#init-only-access-on-conversion-on-this)
|
||||
2. [Record structs](#record-structs)
|
||||
1. [Copy constructors and Clone methods](#copy-constructors-and-clone)
|
||||
2. [`PrintMembers`](#printmembers)
|
||||
3. [Implemented equality algorithms](#implemented-equality-algorithms)
|
||||
4. [Field initializers](#field-initializers)
|
||||
5. [GetHashcode determinism](#gethashcode-determinism)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "You don't see the gazillion tabs I have in my other window... It's actually mostly stackoverflow posts on how to use System.IO.Pipelines."
|
||||
|
||||
## Discussion
|
||||
|
||||
### Init-only access on conversion on `this`
|
||||
|
||||
https://github.com/dotnet/roslyn/issues/50053
|
||||
|
||||
We have 3 options on this issue, centered around how whether we want to make a change and, if so, how far do we want to take it.
|
||||
|
||||
1. Change nothing. The scenario remains an error.
|
||||
2. Allow unconditional casts.
|
||||
3. Allow `as` casts as well.
|
||||
|
||||
We feel that this case is pretty pathological, and we have trouble coming up with real-world examples of APIs that would need to
|
||||
both hide a public member from a base type and initialize it in the constructor to some value. It would also be odd to allow it
|
||||
in the constructor while not having a form of initializing the property from an object initializer, which is the new thing that
|
||||
`init` enables over `set` methods. If we continue seeing a need to name hidden members, perhaps we can come up with a feature that
|
||||
generally allows that, as opposed to solving one particular case in constructors.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll go with 1. The proposal is rejected.
|
||||
|
||||
### Record structs
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4334
|
||||
|
||||
#### Copy constructors and Clone methods
|
||||
|
||||
We're revisiting the decision made the last time we talked about record structs. In that meeting, we decided to disallow record
|
||||
structs from defining customized `with` semantics, due to concerns over how such structs would behave in generic contexts when
|
||||
constrained to `where T : struct`. If we do disallow this customization, do we need to disallow methods named Clone as well?
|
||||
And should we also disallow copy constructors? In looking at the questions here, we spitballed some potential ways that we could
|
||||
allow customized copies and still allow record structs to be used in generics constrained to `where T : struct`, potentially by
|
||||
introducing a new interface in the BCL. A `with` on a struct type param would check for an implementation of that interface and
|
||||
call that, rather than blindly emitting a `dup` instruction as our original intention was. We think it's an interesting idea and
|
||||
want to pull it into a complete proposal, so we're holding off on making any decisions about allowed and disallowed members in
|
||||
record structs related to copying for now.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
On hold.
|
||||
|
||||
#### `PrintMembers`
|
||||
|
||||
A question was raised during initial review of the specification for record structs on whether we need to keep `PrintMembers`.
|
||||
Struct types don't have inheritance, so we could theoretically simplify that to just `ToString()` for this case. However, we
|
||||
think that there is value in minimizing the differences between record structs and record classes, so conversion between them
|
||||
is as painless as possible. Since users can provide their own `PrintMembers` method with their own semantics, removing it
|
||||
potentially introduces friction in making such a change.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Keep `PrintMembers`. The behavior will be the same as in record classes.
|
||||
|
||||
#### Implemented equality algorithms
|
||||
|
||||
During review, a question was raised about our original decision here with respect to generating the actual equality implementation
|
||||
for record structs. Originally, we had decided to not generate a new `Equals(object)` method, and have making a struct a record
|
||||
be solely about adding new surface area for the same functionality. Instead, we'd work with the runtime to make the equality
|
||||
generation better for all struct types. While we still want to pursue this angle as well, after discussion we decided that this
|
||||
would be another friction point between record classes and record structs, and could potentially have negative consequences if
|
||||
we don't have time to ship better runtime equality for structs in .NET 6, as many scenarios would then just need to turn around
|
||||
and implement equality themselves. In the future, if we do get the better runtime-generated equality, that could be added as a
|
||||
feature flag to `System.Runtime.CompilerServices.RuntimeFeature`, and we can inform the generation of equality based on the
|
||||
presence of the flag.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We will generate equality methods in the same manner as proposed in the record struct specification proposal.
|
||||
|
||||
#### Field initializers
|
||||
|
||||
The question here is whether we can allow field initializers in structs that have a primary constructor with more than zero
|
||||
parameters. The immediate followup to that question, of course, is can we just finally allow parameterless constructors for
|
||||
structs in general, and then field initializers just work for all of them? We're still interested in doing this: the
|
||||
`Activator.CreateInstance` bug was in a version of the framework that is long out of support at this point, and we have universal
|
||||
agreement behind the idea. The last time we talked about the feature we took a look at non-defaultable value types in general,
|
||||
and while there are interesting ideas in there, we don't think we need to block parameterless constructors on it.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Let's dig up the proposal from when we did parameterless struct constructors last and get it done, then this question becomes
|
||||
moot.
|
||||
|
||||
#### GetHashcode Determinism in `Combine`
|
||||
|
||||
The record class specification, and the record struct specification, states:
|
||||
|
||||
> The synthesized override of `GetHashCode()` returns an `int` result of a deterministic function combining the values of
|
||||
> `System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)` for each instance field `fieldN` with `TN` being
|
||||
> the type of `fieldN`.
|
||||
|
||||
We're not precise on the semantics of "a deterministic function combining the values" here, and the question is whether we should
|
||||
be more precise about the semantics of that. After discussion, we believe we're fine with the wording. It does not promise
|
||||
determinism across boundaries such as different executions of the same program or running the same program on different versions
|
||||
of the runtime, which are not guarantees we want to make.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Fine as is.
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
# C# Language Design Meeting for Feb 3rd, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [List patterns on `IEnumerable`](#list-patterns-on-ienumerable)
|
||||
2. [Global usings](#global-usings)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "If the teacher doesn't show up, class is dismissed, right?" "Who is the teacher in this scenario?"
|
||||
|
||||
## Discussion
|
||||
|
||||
### List patterns on `IEnumerable`
|
||||
|
||||
https://github.com/alrz/csharplang/blob/list-patterns/proposals/list-patterns.md
|
||||
|
||||
Today, we discussed what the behavior of list patterns should be for `IEnumerable`, and specifically how much of list patterns
|
||||
should be able to operate on enumerables. There are multiple competing factors here that we need to take into consideration.
|
||||
|
||||
1. `IEnumerable`s can be infinite. If a user were to attempt to match the count of such an enumerable, their code hangs.
|
||||
2. `IEnumerable`s can be expensive. Likely more common than the infinite case, enumerable can be a DB query that needs to run
|
||||
off to some data server in the cloud when queried. We absolutely do not want to enumerate these multiple times.
|
||||
3. We do not want to introduce a new pitfall of "Oh, you're in a hot loop, remove that pattern match because it'll be slower
|
||||
than checking the pattern by hand".
|
||||
|
||||
All of that said, we do think that list patterns on enumerables are useful. While this can be domain specific, efficient enumeration
|
||||
of enumerables is relatively boilerplate code and with some smart dependence on framework APIs, we think there is a path forward.
|
||||
For example, the runtime just approved a new API for [`TryGetNonEnumeratedCount`](https://github.com/dotnet/runtime/issues/27183),
|
||||
and in order to make the pattern fast we could attempt to use it, then fall back to a state-machine-based approach if the collection
|
||||
must be iterated. This would give us the best of both worlds: If the enumerable is actually backed by a concrete list type, we don't
|
||||
need to do any enumeration of the enumerable to check the length pattern. If it's not, we can fall back to the state machine, which
|
||||
can do a more efficient enumeration while checking subpatterns than we could expose as an API from the BCL.
|
||||
|
||||
For the state machine fallback, we want to be as efficient as possible. This means not enumerating twice, and bailing out as soon
|
||||
as possible. So, the pattern `enumerable [< 6] { 1, 2, 3, .., 10 }` can immediately return false if it gets to more than 6 elements,
|
||||
or if any of the first 3 elements don't match the supplied patterns.
|
||||
|
||||
Finally, on the topic of potentially infinite or expensive enumerations, they are an existing problem today. The BCL exposes a `Count`
|
||||
API, and if you call it on a Fibonacci sequence generator, your program will hang. Enumerating db calls is expensive, regardless
|
||||
of whether we provide a new, more succinct form or not. In these cases, users generally know what they're working with: it's not a
|
||||
surprise that they have an infinite enumerable, they've very likely already done a `Take` or some other subsequence mechanism if they're
|
||||
looking for "the last element from the end". By having these patterns, we simply allow these users to take advantage of a generation
|
||||
strategy that's as efficient as they could write by hand, with much clearer intent. As long as the enumeration has a specific pattern
|
||||
that users can reason about, it's an overall win.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll proceed with making a detailed specification on how `IEnumerable` will be pattern matched against. We're ok with taking advantage
|
||||
of BCL APIs here, including `TryGetNonEnumeratedCount`, and are comfortable working with the BCL team to add new APIs if existing ones
|
||||
don't prove complete enough for our purposes.
|
||||
|
||||
### Global usings
|
||||
|
||||
We started this by looking at a prototype of how ASP.NET is looking to reduce ceremony in their templates with a framework called
|
||||
Feather, which can be seen [here](https://github.com/featherhttp/framework). The hello world for this code is 12 lines long: 6 lines
|
||||
of actual code, 3 newlines, and 3 lines of usings. As apps get more complicated, these usings tend to grow quite quickly, and they're
|
||||
all for the types of things that often boil down to "I want to use the async feature from C# 5, LINQ from C# 3, generic collections
|
||||
from C# 2, and I want to build an ASP.NET application". This hints at a related, but orthogonal, using feature: recursive usings. For
|
||||
example, `using System.*` would bring in all namespaces under `System`, or `using Microsoft.AspNetCore.*` would bring in all namespaces
|
||||
under `Microsoft.AspNetCore`. However, such a feature wouldn't really solve the issue in question here, which is "how can specifying
|
||||
the SDK in use ensure that I get the ability to use the features of that SDK by default?"
|
||||
|
||||
We have 2 general approaches here: use the project file as the place where implicit usings go, or allow a source file to include them.
|
||||
Both approaches have several pros and cons. In a project file works more natively for an SDK, as they can just define a property. The
|
||||
SDK does define an AssemblyVersion.cs today, but this feature is potentially more complicated than that. The project file is also
|
||||
where we tend to put these types of global controls, like nullable or checked. On the other hand, project files are very hard to tool,
|
||||
as MSBuild is a complicated beast that can do arbitrary things. Artificial restrictions on the feature, like requiring that it appear
|
||||
directly in the project file and not in some other targets file, severely limits the usefulness of the feature across solutions. Source
|
||||
files as the solution provide an easily-toolable experience that feels more C#-native, but potentially encourages these usings to be
|
||||
spread out in many locations. Razor has a `_ViewImports.cshtml` file that handles this problem for Razor files, but we don't think this
|
||||
maps well to the solutions we're discussing for C#: it only allows the one file, and is in some ways the "project file" for the rest
|
||||
of the cshtml files in the solution as it provides things like the namespace of the rest of the pages.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We're split right down the middle here between project file and C# files. We'll revisit this again very shortly to try and make
|
||||
progress on the feature.
|
|
@ -1,118 +0,0 @@
|
|||
# C# Language Design Meeting for Feb 8th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. Virtual statics in interfaces
|
||||
1. [Syntax Clashes](#syntax-clashes)
|
||||
2. [Self-applicability as a constraint](#self-applicability-as-a-constraint)
|
||||
3. [Relaxed operator operand types](#relaxed-operator-operand-types)
|
||||
4. [Constructors](#constructor)
|
||||
|
||||
## Quote(s) of the Day
|
||||
|
||||
- "If you need to kick yourself I see that [redacted] has a foot in the chat you can kick yourself with"
|
||||
- "Are we at the point where we derail your meeting with other proposals?"
|
||||
- "It's the classic, noble art of retconning"
|
||||
|
||||
## Discussion
|
||||
|
||||
Today, we want to take a look at a few top-level design decisions for virtual statics in interfaces that will drive further design and implementation
|
||||
decisions for this feature.
|
||||
|
||||
### Syntax Clashes
|
||||
|
||||
C# 8 brought 2 new features to interfaces: default members, and non-virtual static members. This sets up a clash between static virtual members and
|
||||
static non-virtual members. In pre-C# 8, `interface` members were always virtual and abstract. C# 8 blurred the line here: private and static members
|
||||
in interfaces are non-virtual. For private members, this makes perfect sense, as the concept of a virtual private method is an oxymoron: if you can't
|
||||
see it, you can't override it. For statics, it's a bit more unfortunate because we now have inconsistency in the default-virtualness of members in
|
||||
the interface. We have a few options for addressing this inconsistency:
|
||||
|
||||
1. Accept life as it is. We'd require `abstract` and/or `virtual` on static members to mark them as such. There is some benefit here: `static` has
|
||||
meant something for 21 years in C#, and that is not `virtual`. Requiring a modifier means this does not change.
|
||||
2. Break the world. Make static members in interfaces mean static virtual by default. This gets us consistency, but breaks consumers in some fashion
|
||||
and/or introduces multiple dialects of C# to support.
|
||||
3. We could introduce a bifurcation in interfaces with a `virtual` modifier on the `interface` declaration itself. When marked with `virtual`, `static`
|
||||
members of the interface are automatically `virtual` by default. This is very not C#-like: we require `static` on all members of `static class`es, why
|
||||
would `virtual interface`s be any different here? And how would you define the existing non-`virtual` members in such an interface?
|
||||
|
||||
Options 2 and 3 also have a question of how they will apply to class members. Due to the size of the changes required we may have to split virtual
|
||||
static members, shipping with just interfaces in the first iteration and adding class types at a later point. However, we need to make sure that we
|
||||
design the language side of these changes together, so when class virtual statics are added they don't feel like an afterthought. The second and third
|
||||
proposals would likely need to have the first proposal for the class version of the feature anyway. While it would be consistent with instance members,
|
||||
neither of them would totally eliminate the needs to apply the modifiers to interface members.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will require `abstract` or `virtual` be applied to a virtual static member. We will also look at allowing these modifiers for instance interface
|
||||
methods, even though they are redundant, much like we allow `public` on members today.
|
||||
|
||||
### Self-applicability as a constraint
|
||||
|
||||
`abstract` static members introduce an interesting scenario in which an interface is no longer valid as a substitute for a type parameter constrained
|
||||
to that interface type. Consider this scenario:
|
||||
|
||||
```cs
|
||||
interface IHasStatics
|
||||
{
|
||||
abstract static int GetValue(); // No implementation of GetValue here
|
||||
}
|
||||
|
||||
class C : IHasStatics
|
||||
{
|
||||
static int GetValue() => 0;
|
||||
}
|
||||
|
||||
void UseStatics<T>() where T : IHasStatics
|
||||
{
|
||||
int v = T.GetValue();
|
||||
}
|
||||
|
||||
UseStatics<C>(); // C satisfies constraint
|
||||
UseStatics<IHasStatics>(); // Error: IHasStatics doesn't satisfy constraint
|
||||
```
|
||||
|
||||
The main question here is what do we do about this? We have 2 paths:
|
||||
|
||||
1. Forbid interfaces with an abstract static from satisfying a constraint on itself.
|
||||
2. Forbid access to static virtuals with a type parameter unless you have an additional constraint like `concrete`.
|
||||
|
||||
Option 2 seems weird here. Why would a user have constrained to a type that implements an interface, rather than just taking the interface, unless
|
||||
they wanted to use these methods? Yes, adding an `abstract static` method to an interface where one does not exist today would be a breaking change
|
||||
for consumers, but that's nothing new: that's why we added DIMs in the first place, and it would continue to be possible to avoid the break by providing
|
||||
a default method body for the virtual method, instead of making it abstract.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We choose option 1.
|
||||
|
||||
### Relaxed operator operand types
|
||||
|
||||
Today, C# requires that at least one of the operand-types of a user-defined operator be the current type. This breaks down with user-defined virtual
|
||||
operators in interfaces, however, as the type in the operator won't be the current type, it will be some type derived from the current type. Here,
|
||||
we naturally look at self types as a possible option. We are concerned with the amount of work that self-types will require, however, and aren't sure
|
||||
that we want to tie the shipping of virtual statics to the need for self types (and any other associated-type feature). We also need to make sure that
|
||||
we relax operators enough, and define BCL-native interfaces in a way, such that asymmetric types are representable. For example, a matrix type would
|
||||
want to be able to add a matrix and a numeric type, and return a matrix. Or `byte`'s `+` operator, which does not return a `byte` today. Given that,
|
||||
we think it is alright to ship this feature and define a set of operator interfaces without the self type, as we would likely be forced to not use it
|
||||
in the general interfaces anyway to keep them flexible enough for all our use cases.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We're ok with relaxing the constraints as much as we need to here. We won't block on self types being in the language.
|
||||
|
||||
### Constructors
|
||||
|
||||
Finally, we looked at allowing or disallowing constructors as virtual in interfaces. This is an interesting area: either derived types would be required
|
||||
to provide a constructor that matches the interface, or derived types would be allowed to not implement interfaces that their base types do. The feature
|
||||
itself is a parallel to regular static methods; in order to properly describe it in terms of static methods, you'd need to have a self type, which is
|
||||
what makes it hard to describe in today's C# terms in the first place. Adding this also brings in the question of what do we do about `new()`? This may
|
||||
be an area where we should prefer a structural approach over a nominal approach, or add a form of type classes to the language: if we were to effectively
|
||||
deprecate `new()`, that would mean that every type that has a parameterless constructor would need to implement an `IHasConstructor` interface instead.
|
||||
And we would need to have infinite variations of that interface, and add them to every type in the BCL. This would be a serious issue, both in terms of
|
||||
sheer surface area required and in terms of effect on type loads and runtime performance penalties for thousands and thousands of new types.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We will not have virtual constructors for now. We think that if we add type classes (and allow implementing a type class on a type the user doesn't own),
|
||||
that will be a better place for them. If we want to improve the `new()` constraint in the mean time, we can look at a more structural form, and users
|
||||
can work around the lack of additional constraints for now by using a static virtual.
|
|
@ -1,168 +0,0 @@
|
|||
# C# Language Design Meeting for Feb 10th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Follow up on record equality](#follow-up-on-record-equality)
|
||||
2. [Namespace directives in top-level programs](#namespace-directives-in-top-level-programs)
|
||||
3. [Global usings](#global-usings)
|
||||
4. [Triage](#triage)
|
||||
1. [Nominal And Collection Deconstruction](#nominal-and-collection-deconstruction)
|
||||
2. [Sealed record ToString](#sealed-record-ToString)
|
||||
3. [`using` aliases for tuple syntax](#using-aliases-for-tuple-syntax)
|
||||
4. [Raw string literals](#raw-string-literals)
|
||||
5. [Allow `var` variables to be used in a `nameof` in their initializers](#allow-var-variables-to-be-used-in-a-nameof-in-their-initializers)
|
||||
6. [First-class native integer support](#first-class-native-integer-support)
|
||||
7. [Extended property patterns](#extended-property-patterns)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "That's 3 issues in 8 minutes." "That's an LDM record!"
|
||||
- "Back to serious stuff. Raw string literals. Because raw is better for digestion"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Follow up on record equality
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/39#issuecomment-678097433
|
||||
https://github.com/dotnet/csharplang/discussions/4411
|
||||
|
||||
Back when the linked comment on #39 was raised, we had an internal email chain discussing changing the implementation of `==` to move the
|
||||
reference equality short-circuit down to the strongly-typed `Equals` implementation. We came to the conclusion that instead of moving the
|
||||
check we should duplicate it, to ensure that `==` still behaves correctly for `null`, but we never followed up on that conclusion. Today,
|
||||
we confirmed that we do indeed want to do that, and update the C# compiler to generate this better version of equality.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We'll do this. https://github.com/dotnet/roslyn/issues/51136 tracks following up on that work.
|
||||
|
||||
### Namespace directives in top-level programs
|
||||
|
||||
Our last discussion on the namespace directive had one open question left: should we allow them in combination with top-level statements?
|
||||
We had an internal email chain on this topic since then, and came to the conclusion that we should disallow this combination. There's a
|
||||
couple reasons for this:
|
||||
|
||||
1. We think the namespace directive is confusing here. It looks like a statement, but the rest of the statements on the top-level don't
|
||||
introduce a new scope.
|
||||
2. It is easier to remove an error later if we decide that it's too restrictive. If we allow it, we'd have to support it forever.
|
||||
3. Users might expect to be able to put a namespace _before_ those top-level statements and change the namespace the implicit `Main` is
|
||||
generated into.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Decision upheld. Top-level statements are not allowed in combination with file-scoped namespace directives.
|
||||
|
||||
### Global usings
|
||||
|
||||
Our last discussion on global usings had us split right down the middle on whether to make global usings a C# syntax feature, or a
|
||||
command-line switch to the compiler, with a razor-thin majority leaning to C# syntax. In order to make progress here to begin
|
||||
implementation, we again took an internal email chain to discuss this further. This conversation drew a few conclusions:
|
||||
|
||||
* Neither a a command-line switch nor C# syntax would prevent a source-generator from providing their own set of usings, but the switch
|
||||
would prevent the source-generator from _dynamically_ providing this, it would have to be predefined in props/targets file in the
|
||||
generator nuget package.
|
||||
* We really need to be considering the experience when using `dotnet`, not `csc`. The SDK passes 182 parameters to a build of a simple
|
||||
hello world application today. It's very unrealistic to base scenarios on calling `csc` directly.
|
||||
* For the base templates, such as as a console app or a default ASP.NET application, this choice doesn't really affect the files the
|
||||
templates drop down. In either case, the usings will be hidden. For more complicated templates, this choice changes whether the template
|
||||
drops a `GlobalUsings.cs` file, or fills in a property in the template csproj. In either case, the template has really moved beyond a
|
||||
single-file program, so the difference isn't huge.
|
||||
* We've had a number of requests over the years for global aliases, and a C# syntax will fit in nicely with such a feature.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
Given the need to start implementing here and the thin majority that prefer C# syntax, we will start investigation and implementation
|
||||
on the C# syntax version, with a speclet forthcoming later this week.
|
||||
|
||||
### Triage
|
||||
|
||||
#### Nominal and Collection Deconstruction
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4082
|
||||
|
||||
This feature will add symmetry with tuple patterns/deconstruction, which is desirable. There is some potential parsing challenges, as
|
||||
several of the examples look like a block with a labelled statement inside it and will need some tricky work to ensure disambiguation
|
||||
works.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
To the backlog. We like the idea and think there's a way to get it into a form we like, but can't commit to it right now.
|
||||
|
||||
#### Sealed record ToString
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4174
|
||||
|
||||
This is a simple enough change and we've had a few complaints about the behavior and inability to seal. As an alternative, we could
|
||||
design a parallel ToString implementation for records that the base type in a hierarchy will call into, which would be a minor behavior
|
||||
change from C# 9 as released. However, this would be complicated, and `sealed` is the keyword to tell consumers "No really, this is the
|
||||
final version of this behavior, you can't change it."
|
||||
|
||||
##### Conclusion
|
||||
|
||||
We'll put this in Any Time to promote community contributions for this small feature, but if no one picks it up we will likely put in a
|
||||
bit of effort to get it into C# 10.
|
||||
|
||||
#### `using` aliases for tuple syntax
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4284
|
||||
|
||||
The title of this issue is a bit of a misnomer, as the proposal actually extends to all types, not just tuples. We've also looked at a
|
||||
broader proposal recently in conjunction with the global using statement feature, which would allow things like
|
||||
`global using MyDictionary<TValue> = System.Collections.Generic.Dictionary<int, TValue>` and other similar work. It seems like we want
|
||||
to make some improvements in the alias and using area, and we should consider doing a subset of that larger proposal now in the same
|
||||
fashion that we've continuously shipped incremental updates for patterns.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Triaged into the working set. We want to make a general using improvement push in 10, and have a goal of at least doing this much for
|
||||
alias improvements in this release.
|
||||
|
||||
#### Raw string literals
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4304
|
||||
|
||||
There are a number of potentially contentious design decisions in this area, but we intentionally tried to steer away from them today
|
||||
and limit to just the general concept. There are many scenarios for embedding other languages into C#: XML, JSON/Javascript, C#, and
|
||||
more. In many of these languages, " and ' are not interchangeable, so anything with a " in it is painful in C# today. We also thing that
|
||||
interpolation in these strings is important, as these embedded code scenarios are often used for templating. There is potential work around
|
||||
a way to specify what character sequence defines the interpolation holes, but we did not go into details or specifics here.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Into the Working Set. There's a bunch of design questions that we'll need to spend some time working on.
|
||||
|
||||
#### Allow `var` variables to be used in a `nameof` in their initializers
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4384
|
||||
|
||||
This is a minor convenience issue that has come up a few times for users, but has potential implementation nastiness with disambiguating
|
||||
whether `nameof` is a method or a `nameof_expression`. Given the minor benefit and the potential implementation concerns, we're concerned
|
||||
about doing this unless we decide to make `nameof` a non-conditional keyword.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Rejected. If we ever make the change to make `nameof` not a conditional keyword and can simplify the binding here, then we can bring this
|
||||
back, but until that point we will leave this as is.
|
||||
|
||||
|
||||
#### First-class native integer support
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4385
|
||||
|
||||
As the language is specified today, compliant C# compilers have to emit certain overflow conversions and then roundtrip those conversions
|
||||
back to the original representation. This is inefficient and a small spec change will produce no observable semantic difference, while
|
||||
allowing the compiler to emit better code.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Accepted into the working set.
|
||||
|
||||
#### Extended property patterns
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4394
|
||||
|
||||
This is something that patterns are very verbose about today, and it's a syntax we've previously discussed as a potential shorthand to
|
||||
reduce the boilerplate. We could also potentially extend this idea to object initializers, but don't want to tie that to this proposal.
|
||||
|
||||
##### Conclusion
|
||||
|
||||
Accepted into the working set.
|
|
@ -1,86 +0,0 @@
|
|||
# C# Language Design Meeting for Feb 22nd, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Global `using`s](#global-usings)
|
||||
2. [`using` alias improvements](#using-alias-improvements)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
* "You're muted" "I wanted to be muted"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Global `using`s
|
||||
|
||||
https://github.com/dotnet/csharplang/blob/master/proposals/csharp-10.0/GlobalUsingDirective.md
|
||||
|
||||
Today we discussed the proposed syntax and restrictions on the current feature specification. As checked in today, the proposal
|
||||
puts `global` after the `using` directive, which could be potentially ambiguous and require complicated parsing logic, as `global`
|
||||
is a valid namespace identifier today. For example, this is valid code:
|
||||
|
||||
```cs
|
||||
// Is this a global using alias, or a top-level using variable declaration?
|
||||
using global myVar = Test.AGlobal;
|
||||
|
||||
class Test
|
||||
{
|
||||
public static global AGlobal = new global();
|
||||
}
|
||||
|
||||
class global : IDisposable { public void Dispose() {} }
|
||||
```
|
||||
|
||||
We could potentially resolve this in the same way we did for the `record` keyword, which is to forbid types be named `global` in
|
||||
C# 10. We haven't gotten pushback on this approach for `record` as a type name so far, which is positive. However, aside from the
|
||||
ambiguity, there are other reasons to view `global`-first as a good thing. `global` here is a modifier on the `using`, which C#
|
||||
generally puts before the declaration, not after. We also considered 2 separate but related questions:
|
||||
|
||||
1. Is `static` a similar modifier? Should it be allowed to come before the `using` as well?
|
||||
* After discussion, we don't believe so. `static` isn't a modifier on the `using`, it fundamentally changes what the meaning
|
||||
of the using is about.
|
||||
2. We already have existing modifiers for C# scopes: should we use `internal` as the keyword here?
|
||||
* Using `internal` begs the followup question: what does `public` on one of these `using`s mean? What about `private`? We're
|
||||
uncomfortable with the idea of treating these more like types than like macros. This starts to get into a very slippery slope
|
||||
of how we export aliases publicly, can additional constraints be added to aliases, and how does that interact with roles?
|
||||
|
||||
Finally, we discussed the proposed restrictions on the feature. We like them overall: if `global` and non-`global` `using`s can
|
||||
be interspersed, it could lead to user confusion as to whether regular `using` directives can affect `global` ones. It also helps
|
||||
set up a clearly-defined set of scopes. We add a new `global` scope that comes before all top-level `using` statements today, and
|
||||
their interactions mirror the interactions of regular `using`s statements with those nested in a namespace.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
We put `global` before `using`, but the proposal is otherwise accepted as is.
|
||||
|
||||
### `using` alias improvements
|
||||
|
||||
https://github.com/dotnet/csharplang/pull/4452
|
||||
|
||||
Continuing with the theme of `using` improvements, we also discussed generalized improvements to `using` aliases to address a
|
||||
number of reoccuring complaints over the years with limitations in today's approach. Specifically, `using` directives today
|
||||
cannot reference predefined names like `string`, they can't have generic type arguments, they can't use tuple syntax, or use
|
||||
pointer types (including the C# 9 function pointer syntax). The current proposal allows all of these things, including aliases
|
||||
for partially-bound type parameters such as `using MyDictionary<T> = System.Collections.Generic.Dictionary<int, T>;`. When
|
||||
combined with the previous `global using` feature, enabling this would allow compilation-unit type aliases. Our remaining open
|
||||
questions here focus again on how we want to push `using`s forward in the future:
|
||||
|
||||
1. If we want to push `using` aliases as real types, then they need to have the ability to expression the same things all other
|
||||
types can, including constraints and variance. If we go this route, we could potentially have a future where you can introduce
|
||||
an alias that adds a _new_ constraint onto an existing type, such as an invariant `IEnumerable<T>` alias, or a dictionary that
|
||||
is constrained to only `struct`-type values.
|
||||
2. If we want to push `using` aliases as more macro-expansion, then the ability to expression constraints and variance is a
|
||||
confusing detriment. The type parameter will be substituted at expansion site and we'll error at that point if an illegal
|
||||
substitution is made.
|
||||
|
||||
Approach 1 has many followup questions that quickly start to slide into roles territory. Aliases can be used in public API, for
|
||||
example, so how would we advertise them publicly if the alias has added new constraints? Is it possible for users to introduce
|
||||
an opaque alias, such as aliasing `int` to `CustomerId` in such a way as there's no implicit conversion between them? Ultimately,
|
||||
we think that these problems are better solved by a discrete language feature such as roles, rather than as an expansion on
|
||||
`using` aliases.
|
||||
|
||||
#### Conclusion
|
||||
|
||||
`using` aliases will be treated more as macro expansions than as real types. We'll check substitutions for concrete types where
|
||||
we can (such as erroring in the alias itself for `using MyList = System.Collections.Generic.List<int*>;`), but for things we
|
||||
cannot check at the declaration site, we will error at the use site. There is no way to add constraints to a `using` alias.
|
|
@ -1,55 +0,0 @@
|
|||
# C# Language Design Meeting for Feb 24th, 2021
|
||||
|
||||
## Agenda
|
||||
|
||||
1. [Static abstract members in interfaces](#static-abstract-members-in-interfaces)
|
||||
|
||||
## Quote of the Day
|
||||
|
||||
- "I'm not using the monoid word, I'm trying to make it relatable"
|
||||
|
||||
## Discussion
|
||||
|
||||
### Static abstract members in interfaces
|
||||
|
||||
https://github.com/dotnet/csharplang/issues/4436
|
||||
|
||||
Today we went over the proposed specification for `static` abstract members. In order to scope the initial implementation, this proposal
|
||||
intentionally limits such members to _only_ `abstract` members, not `virtual` members. This is due to complexity in passing along the
|
||||
"instance" that the static is operating on. Consider this example:
|
||||
|
||||
```cs
|
||||
interface I
|
||||
{
|
||||
virtual static void I1() { I2(); }
|
||||
abstract static I2();
|
||||
}
|
||||
|
||||
class C : I
|
||||
{
|
||||
public static void I2() {}
|
||||
}
|
||||
|
||||
C.I1();
|
||||
// How does the runtime pass along the information that the type here is C,
|
||||
// and I.M1() should invoke C.I2()?
|
||||
```
|
||||
|
||||
Given the complexity of implementation of this scenario and the lack of current motivating examples, we will push this out for a later
|
||||
version unless our investigations of representing generic math end up needing it.
|
||||
|
||||
#### `==` and `!=` operators
|
||||
|
||||
These are restricted in interfaces today because interface types cannot implement `object.Equals(object other)` and `object.GetHashcode()`,
|
||||
which are integral to implementing the operators correctly. We can lift that restriction for `abstract static` members, as the implementing
|
||||
concrete type will then be required to provide implementations of `Equals(object other)` and `GetHashcode()`.
|
||||
|
||||
#### `sealed` on non-abstract members
|
||||
|
||||
We don't have any strong feelings on allowing `sealed` on non-virtual `static` interface members. It's fine to include in the proposal.
|
||||
|
||||
#### Conversion restrictions
|
||||
|
||||
As we implement a set of math interfaces, we'll come across parts of the current restrictions that make it impossible. We should be cautious
|
||||
here and only lift restrictions as much as is necessary to implement the target scenarios. We can review the rules in depth when they have
|
||||
been proven out by final usage.
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue