Merge from dotnet/csharplang
Merge from dotnet/csharplang
This commit is contained in:
commit
d6718accc5
37 changed files with 1393 additions and 151 deletions
|
@ -1,111 +1,6 @@
|
|||
Features Added in C# Language Versions
|
||||
====================
|
||||
|
||||
# [C# 1.0](https://en.wikipedia.org/wiki/Microsoft_Visual_Studio#.NET_.282002.29) - Visual Studio .NET 2002
|
||||
|
||||
- Classes
|
||||
- Structs
|
||||
- Interfaces
|
||||
- Events
|
||||
- Properties
|
||||
- Delegates
|
||||
- Expressions
|
||||
- Statements
|
||||
- Attributes
|
||||
- Literals
|
||||
|
||||
# [C# 1.2](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history#c-version-12) - Visual Studio .NET 2003
|
||||
|
||||
- Dispose in foreach
|
||||
- foreach over string specialization
|
||||
|
||||
# [C# 2](https://msdn.microsoft.com/en-us/library/7cz8t42e(v=vs.80).aspx) - Visual Studio 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) - Visual Studio 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) - Visual Studio 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/) - Visual Studio 2012
|
||||
- Asynchronous methods
|
||||
- Caller info attributes
|
||||
- foreach loop was changed to generates a new loop variable rather than closing over the same variable every time
|
||||
|
||||
# [C# 6](https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6) - 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
|
||||
- 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 - .NET Core 3.0 and Visual Studio 2019 version 16.3
|
||||
- [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).
|
||||
|
@ -121,3 +16,106 @@ 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/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# 6](https://github.com/dotnet/roslyn/wiki/New-Language-Features-in-C%23-6) - 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
|
||||
- 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# 5](https://blogs.msdn.microsoft.com/mvpawardprogram/2012/03/26/an-introduction-to-new-features-in-c-5-0/) - Visual Studio 2012
|
||||
- Asynchronous methods
|
||||
- Caller info attributes
|
||||
- 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/en-us/magazine/ff796223.aspx) - Visual Studio 2010
|
||||
- Dynamic binding
|
||||
- Named and optional arguments
|
||||
- Co- and Contra-variance for generic delegates and interfaces
|
||||
- Embedded interop types ("NoPIA")
|
||||
|
||||
# [C# 3](https://msdn.microsoft.com/en-us/library/bb308966.aspx) - Visual Studio 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# 2](https://msdn.microsoft.com/en-us/library/7cz8t42e(v=vs.80).aspx) - Visual Studio 2005
|
||||
- Generics
|
||||
- Partial types
|
||||
- Anonymous methods
|
||||
- Iterators
|
||||
- Nullable types
|
||||
- Getter/setter separate accessibility
|
||||
- Method group conversions (delegates)
|
||||
- Static classes
|
||||
- Delegate inference
|
||||
|
||||
# [C# 1.2](https://docs.microsoft.com/en-us/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
|
||||
- Structs
|
||||
- Interfaces
|
||||
- Events
|
||||
- Properties
|
||||
- Delegates
|
||||
- Expressions
|
||||
- Statements
|
||||
- Attributes
|
||||
- Literals
|
159
meetings/2020/LDM-2020-01-15.md
Normal file
159
meetings/2020/LDM-2020-01-15.md
Normal file
|
@ -0,0 +1,159 @@
|
|||
|
||||
# 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.
|
108
meetings/2020/LDM-2020-01-22.md
Normal file
108
meetings/2020/LDM-2020-01-22.md
Normal file
|
@ -0,0 +1,108 @@
|
|||
|
||||
# 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.
|
106
meetings/2020/LDM-2020-01-29.md
Normal file
106
meetings/2020/LDM-2020-01-29.md
Normal file
|
@ -0,0 +1,106 @@
|
|||
|
||||
# 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.
|
61
meetings/2020/LDM-2020-02-03.md
Normal file
61
meetings/2020/LDM-2020-02-03.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
|
||||
# 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.
|
133
meetings/2020/LDM-2020-02-05.md
Normal file
133
meetings/2020/LDM-2020-02-05.md
Normal file
|
@ -0,0 +1,133 @@
|
|||
|
||||
# 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.
|
48
meetings/2020/LDM-2020-02-10.md
Normal file
48
meetings/2020/LDM-2020-02-10.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
# 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.
|
||||
|
116
meetings/2020/LDM-2020-02-12.md
Normal file
116
meetings/2020/LDM-2020-02-12.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
|
||||
# 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."
|
82
meetings/2020/LDM-2020-02-19.md
Normal file
82
meetings/2020/LDM-2020-02-19.md
Normal file
|
@ -0,0 +1,82 @@
|
|||
|
||||
# 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."
|
41
meetings/2020/LDM-2020-02-24.md
Normal file
41
meetings/2020/LDM-2020-02-24.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
|
||||
# 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
|
||||
conceit 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.
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
## Schedule when convenient
|
||||
|
||||
- Is `e is dynamic` a "pure" null check? (Neal)
|
||||
- https://github.com/dotnet/csharplang/issues/2608 module initializers (Neal)
|
||||
- https://github.com/dotnet/csharplang/issues/2910 base(T) (Neal)
|
||||
- https://github.com/dotnet/csharplang/issues/140 `field` keyword in properties (cyrusn)
|
||||
|
||||
## Recurring topics
|
||||
|
||||
|
@ -14,36 +14,53 @@
|
|||
- *Triage milestones*
|
||||
- *Design review*
|
||||
|
||||
## April 15, 2020
|
||||
|
||||
- Is `e is dynamic` a "pure" null check? (Neal)
|
||||
- Reconsider: Target-typing ?: when the natural type isn't convertible to the target type. (Neal)
|
||||
- https://github.com/dotnet/csharplang/issues/2926 Target-typed simple identifier (Neal)
|
||||
- Reconsider: Inferred type of an or pattern is the "common type" of the two inferred types (Neal)
|
||||
|
||||
## April 13, 2020
|
||||
|
||||
## April 8, 2020
|
||||
|
||||
## April 6, 2020
|
||||
|
||||
## April 1, 2020
|
||||
|
||||
## March 30, 2020
|
||||
|
||||
## March 25, 2020
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/3259 Open issues with native integers (Chuck)
|
||||
- Records design (Mads, Andy)
|
||||
|
||||
## March 23, 2020
|
||||
|
||||
- Feedback from MVP Summit on records etc. (Mads, Andy)
|
||||
|
||||
## March 18, 2020
|
||||
|
||||
- https://github.com/jaredpar/csharplang/blob/record/proposals/recordsv3.md clone-style records (Jared)
|
||||
|
||||
## March 11, 2020
|
||||
|
||||
- Records design (Mads, Andy)
|
||||
|
||||
## March 9, 2020
|
||||
|
||||
- Digest feedback from design review
|
||||
- Records design (Mads, Andy)
|
||||
|
||||
## Feb 26, 2020
|
||||
|
||||
- Design review
|
||||
|
||||
## Feb 24
|
||||
|
||||
## Feb 19
|
||||
|
||||
## Feb 12
|
||||
|
||||
## Feb 10
|
||||
|
||||
## Feb 5
|
||||
|
||||
## Feb 3
|
||||
|
||||
## Jan 29, 2020
|
||||
|
||||
- Records: drilling in to individual features (Mads)
|
||||
|
||||
## Jan 22, 2020
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/3117 Top-level statements and functions (Mads)
|
||||
- https://github.com/dotnet/csharplang/issues/3086 Expressions Blocks (Chuck)
|
||||
|
||||
## Jan 15, 2020
|
||||
|
||||
- Records: Where it fits in our *programming with data* theme (Neal)
|
||||
- Records: The individual subfeatures of records (Mads)
|
||||
|
||||
## Jan 13, 2020
|
||||
|
||||
- Records: Paging back in the previous proposal (Andy)
|
||||
|
@ -52,6 +69,65 @@
|
|||
|
||||
Overview of meetings and agendas for 2020
|
||||
|
||||
## 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)
|
||||
|
|
171
proposals/Simple-programs.md
Normal file
171
proposals/Simple-programs.md
Normal file
|
@ -0,0 +1,171 @@
|
|||
# Simple programs
|
||||
|
||||
* [x] Proposed
|
||||
* [x] Prototype: Started
|
||||
* [ ] Implementation: Not Started
|
||||
* [ ] Specification: Not Started
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
Allow a sequence of *statements* to occur right before the *namespace_member_declaration*s of a *compilation_unit* (i.e. source file).
|
||||
|
||||
The semantics are that if such a sequence of *statements* is present, the following type declaration, modulo the actual type name and the method name, would be emitted:
|
||||
|
||||
``` c#
|
||||
static class Program
|
||||
{
|
||||
static async Task Main()
|
||||
{
|
||||
// statements
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See also https://github.com/dotnet/csharplang/issues/3117.
|
||||
|
||||
## Motivation
|
||||
[motivation]: #motivation
|
||||
|
||||
There's a certain amount of boilerplate surrounding even the simplest of programs,
|
||||
because of the need for an explicit `Main` method. This seems to get in the way of
|
||||
language learning and program clarity. The primary goal of the feature therefore is
|
||||
to allow C# programs without unnecessary boilerplate around them, for the sake of
|
||||
learners and the clarity of code.
|
||||
|
||||
## Detailed design
|
||||
[design]: #detailed-design
|
||||
|
||||
### Syntax
|
||||
|
||||
The only additional syntax is allowing a sequence of *statement*s in a compilation unit,
|
||||
just before the *namespace_member_declaration*s:
|
||||
|
||||
``` antlr
|
||||
compilation_unit
|
||||
: extern_alias_directive* using_directive* global_attributes? statement* namespace_member_declaration*
|
||||
;
|
||||
```
|
||||
|
||||
In all but one *compilation_unit* the *statement*s must all be local function declarations.
|
||||
|
||||
Example:
|
||||
|
||||
``` c#
|
||||
// File 1 - any statements
|
||||
if (args.Length == 0
|
||||
|| !int.TryParse(args[0], out int n)
|
||||
|| n < 0) return;
|
||||
Console.WriteLine(Fib(n).curr);
|
||||
|
||||
// File 2 - only local functions
|
||||
(int curr, int prev) Fib(int i)
|
||||
{
|
||||
if (i == 0) return (1, 0);
|
||||
var (curr, prev) = Fib(i - 1);
|
||||
return (curr + prev, curr);
|
||||
}
|
||||
```
|
||||
|
||||
### Semantics
|
||||
|
||||
If any top-level statements are present in any compilation unit of the program, the meaning is as if
|
||||
they were combined in the block body of a `Main` method of a `Program` class in the global namespace,
|
||||
as follows:
|
||||
|
||||
``` c#
|
||||
static class Program
|
||||
{
|
||||
static async Task Main()
|
||||
{
|
||||
// File 1 statements
|
||||
// File 2 local functions
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that the names "Program" and "Main" are used only for illustrations purposes, actual names used by
|
||||
compiler are implementation dependent and neither the type, nor the method can be referenced by name from
|
||||
source code.
|
||||
|
||||
The method is designated as the entry point of the program. Explicitly declared methods that by convention
|
||||
could be considered as an entry point candidates are ignored. A warning is reported when that happens. It is
|
||||
an error to specify `-main:<type>` compiler switch.
|
||||
|
||||
If any one compilation unit has statements other than local function declarations, statements from that
|
||||
compilation unit occur first. This causes it to be legal for local functions in one file to reference
|
||||
local variables in another. The order of statement contributions (which would all be local functions)
|
||||
from other compilation units is undefined.
|
||||
|
||||
Async operations are allowed in top-level statements to the degree they are allowed in statements within
|
||||
a regular async entry point method. However, they are not required, if `await` expressions and other async
|
||||
operations are omitted, no warning is produced. Instead the signature of the generated entry point method
|
||||
is equivalent to
|
||||
``` c#
|
||||
static void Main()
|
||||
```
|
||||
|
||||
The example above would yield the following `$Main` method declaration:
|
||||
|
||||
``` c#
|
||||
static class $Program
|
||||
{
|
||||
static void $Main()
|
||||
{
|
||||
// Statements from File 1
|
||||
if (args.Length == 0
|
||||
|| !int.TryParse(args[0], out int n)
|
||||
|| n < 0) return;
|
||||
Console.WriteLine(Fib(n).curr);
|
||||
|
||||
// Local functions from File 2
|
||||
(int curr, int prev) Fib(int i)
|
||||
{
|
||||
if (i == 0) return (1, 0);
|
||||
var (curr, prev) = Fib(i - 1);
|
||||
return (curr + prev, curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
At the same time an example like this:
|
||||
``` c#
|
||||
// File 1
|
||||
await System.Threading.Tasks.Task.Delay(1000);
|
||||
System.Console.WriteLine("Hi!");
|
||||
```
|
||||
|
||||
would yield:
|
||||
``` c#
|
||||
static class $Program
|
||||
{
|
||||
static async Task $Main()
|
||||
{
|
||||
// Statements from File 1
|
||||
await System.Threading.Tasks.Task.Delay(1000);
|
||||
System.Console.WriteLine("Hi!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Scope of top-level local variables and local functions
|
||||
|
||||
Even though top-level local variables and functions are "wrapped"
|
||||
into the generated entry point method, they should still be in scope throughout the program.
|
||||
For the purpose of simple-name evaluation, once the global namespace is reached:
|
||||
- First, an attempt is made to evaluate the name within the generated entry point method and
|
||||
only if this attempt fails
|
||||
- The "regular" evaluation within the global namespace declaration is performed.
|
||||
|
||||
This could lead to name shadowing of namespaces and types declared within the global namespace
|
||||
as well as to shadowing of imported names.
|
||||
|
||||
If the simple name evaluation occurs outside of the top-level statements and the evaluation
|
||||
yields a top-level local variable or function, that should lead to an error.
|
||||
|
||||
In this way we protect our future ability to better address "Top-level functions" (scenario 2
|
||||
in https://github.com/dotnet/csharplang/issues/3117), and are able to give useful diagnostics
|
||||
to users who mistakenly believe them to be supported.
|
||||
|
5
proposals/csharp-6.0/enum-base-type.md
Normal file
5
proposals/csharp-6.0/enum-base-type.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
## Enum Base Type
|
||||
|
||||
In C# 6.0 we relaxed the syntax of an enum base type to be a type syntax, but require that it bind to one of a specified set of types. So `System.Int32` is permitted. But the spec has not been updated; it still requires one of a set of keywords for the base.
|
||||
|
||||
This is a placeholder for a specification of that change.
|
3
proposals/csharp-7.0/expression-bodied-everything.md
Normal file
3
proposals/csharp-7.0/expression-bodied-everything.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Expression Bodied Everything
|
||||
|
||||
In C# 7.0, we added support for expression-bodied constructors, destructors, and accessors. This is a placeholder for the specification.
|
3
proposals/csharp-7.0/ref-locals-returns.md
Normal file
3
proposals/csharp-7.0/ref-locals-returns.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Ref Locals and Returns
|
||||
|
||||
In C# 7.0 we added support for *ref locals and ref returns*. This is a placeholder for its specification.
|
3
proposals/csharp-7.0/tuples.md
Normal file
3
proposals/csharp-7.0/tuples.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Tuples
|
||||
|
||||
In C# 7.0 we added support for *tuples*. This is a placeholder for its specification.
|
3
proposals/csharp-7.0/value-task.md
Normal file
3
proposals/csharp-7.0/value-task.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Value Task
|
||||
|
||||
In C# 7.0 we added support for *value task* and custom task types and task builders. This is a placeholder for its specification.
|
3
proposals/csharp-7.2/readonly-struct.md
Normal file
3
proposals/csharp-7.2/readonly-struct.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Readonly structs
|
||||
|
||||
In C# 7.2, we added a feature permitting a struct declaration to have the `readonly` modifier. This is a placeholder for that feature's specification.
|
3
proposals/csharp-7.2/ref-extension-methods.md
Normal file
3
proposals/csharp-7.2/ref-extension-methods.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Ref Extension Methods
|
||||
|
||||
In C# 7.2, we added support for *ref extension methods*. This is a placeholder for the feature's specification.
|
3
proposals/csharp-7.2/ref-struct-and-span.md
Normal file
3
proposals/csharp-7.2/ref-struct-and-span.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Ref Structs and Span
|
||||
|
||||
In C# 7.2 we added support for *ref struct* types. This is a placeholder for the specification.
|
3
proposals/csharp-7.3/enum-delegate-constraints.md
Normal file
3
proposals/csharp-7.3/enum-delegate-constraints.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Enum and Delegate type parameter constraint
|
||||
|
||||
In C# 7.3, we added support for type parameter constraint keywords `enum` and `delegate`. This is a placeholder for their specification.
|
3
proposals/csharp-7.3/ref-loops.md
Normal file
3
proposals/csharp-7.3/ref-loops.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Ref loops
|
||||
|
||||
In C# 7.3, we added support for *ref for loops* and *ref foreach loops*. This is a placeholder for their specifications.
|
|
@ -41,9 +41,9 @@ If an element-wise comparison returns some other non-bool type in a tuple equali
|
|||
- if the non-bool type converts to `bool`, we apply that conversion,
|
||||
- if there is no such conversion, but the type has an operator `false`, we'll use that and negate the result.
|
||||
|
||||
In a tuple inequality, the same rules apply except that we'll use the operator `true` (without negation) instead of the operator `true`.
|
||||
In a tuple inequality, the same rules apply except that we'll use the operator `true` (without negation) instead of the operator `false`.
|
||||
|
||||
Those rules are similar the rules involved for using a non-bool type in an `if` statement and some other existing contexts.
|
||||
Those rules are similar to the rules involved for using a non-bool type in an `if` statement and some other existing contexts.
|
||||
|
||||
## Evaluation order and special cases
|
||||
The left-hand-side value is evaluated first, then the right-hand-side value, then the element-wise comparisons from left to right (including conversions, and with early exit based on existing rules for conditional AND/OR operators).
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
## Alternative interpolated verbatim strings
|
||||
|
||||
In C# 8.0 we added a feature that permits an interpolated verbatim string to be introduced with the characters `@$"` or the characters `$@"`. This is a placeholder for its specification.
|
|
@ -156,7 +156,7 @@ An earlier version of this document recommended (1), but we since switched to (4
|
|||
|
||||
The two main problems with (1):
|
||||
- producers of cancellable enumerables have to implement some boilerplate, and can only leverage the compiler's support for async-iterators to implement a `IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken)` method.
|
||||
- it is likely that many producers would be tempted to just add a `CancellationToken` parameter to their async-enumerable signature instead, which will prevent consumers from passing the cancellation token they want when their are given an `IAsyncEnumerable` type.
|
||||
- it is likely that many producers would be tempted to just add a `CancellationToken` parameter to their async-enumerable signature instead, which will prevent consumers from passing the cancellation token they want when they are given an `IAsyncEnumerable` type.
|
||||
|
||||
There are two main consumption scenarios:
|
||||
1. `await foreach (var i in GetData(token)) ...` where the consumer calls the async-iterator method,
|
||||
|
|
3
proposals/csharp-8.0/async-using.md
Normal file
3
proposals/csharp-8.0/async-using.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Async using declaration
|
||||
|
||||
In C# 8.0 we added support for an *async using* statements. There are two forms. One has a block body that is the scope of the using declaratation. The other declares a local and is implicitly scoped to the end of the block. This is a placeholder for their specification.
|
3
proposals/csharp-8.0/constraints-in-overrides.md
Normal file
3
proposals/csharp-8.0/constraints-in-overrides.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Override with constraints
|
||||
|
||||
In C# 8.0, we added a feature to permit the specification of certain type parameter constraints in an `override` method declaration. This is a placeholder for its specification.
|
3
proposals/csharp-8.0/constructed-unmanaged.md
Normal file
3
proposals/csharp-8.0/constructed-unmanaged.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Unmanaged constructed types
|
||||
|
||||
In C# 8.0, we extended the concept of an *unmanaged* type to include constructed (generic) types. This is a placeholder for its specification.
|
3
proposals/csharp-8.0/notnull-constraint.md
Normal file
3
proposals/csharp-8.0/notnull-constraint.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Notnull constraint
|
||||
|
||||
In C# 8.0, we added a language feature that permits the specification of a new type parameter constraint `notnull`. This is a placeholder for its specification.
|
3
proposals/csharp-8.0/obsolete-accessor.md
Normal file
3
proposals/csharp-8.0/obsolete-accessor.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Obsolete on property accessor
|
||||
|
||||
In C# 8.0, we added support for declaring a property accessor `[Obsolete]`. This is a placeholder for the specification.
|
|
@ -86,7 +86,7 @@ public static class MyClass
|
|||
}
|
||||
```
|
||||
|
||||
Readonly can be applied to property accessors to indicate that `this` will not be mutated in the accessor.
|
||||
Readonly can be applied to property accessors to indicate that `this` will not be mutated in the accessor. The following examples have readonly setters because those accessors modify the state of member field, but do not modify the value of that member field.
|
||||
|
||||
```csharp
|
||||
public int Prop1
|
||||
|
|
3
proposals/csharp-8.0/shadowing-in-nested-functions.md
Normal file
3
proposals/csharp-8.0/shadowing-in-nested-functions.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Name shadowing in nested functions
|
||||
|
||||
In C# 8.0, we added a feature that permits parameters and locals in lambdas and local functions to use names that hide/shadow the names of locals or parameters from the enclosing scope. This is a placholder for its specification.
|
3
proposals/csharp-8.0/unconstrained-null-coalescing.md
Normal file
3
proposals/csharp-8.0/unconstrained-null-coalescing.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## Unconstrained type parameter in null coalescing operator
|
||||
|
||||
In C# 8.0 we introduced a feature that permits a null coalescing operator to have a left operand that is not known to be either a reference or value type (i.e. an unconstrained type parameter). This is a placeholder for its specification.
|
|
@ -13,8 +13,7 @@ potential implementation of the feature):
|
|||
|
||||
https://github.com/dotnet/csharplang/issues/191
|
||||
|
||||
This is an alternate design proposal to [compiler intrinsics]
|
||||
(https://github.com/dotnet/csharplang/blob/master/proposals/intrinsics.md)
|
||||
This is an alternate design proposal to [compiler intrinsics](https://github.com/dotnet/csharplang/blob/master/proposals/intrinsics.md)
|
||||
|
||||
## Detailed Design
|
||||
|
||||
|
@ -130,6 +129,18 @@ delegate* managed<string, int>;
|
|||
delegate*<delegate* managed<string, int>, delegate*<string, int>>;
|
||||
```
|
||||
|
||||
### Function pointer conversions
|
||||
|
||||
In an unsafe context, the set of available implicit conversions (Implicit conversions) is extended to include the following implicit pointer conversions:
|
||||
- [_Existing conversions_](https://github.com/dotnet/csharplang/blob/master/spec/unsafe-code.md#pointer-conversions)
|
||||
- From _funcptr\_type_ `F0` to another _funcptr\_type_ `F1`, provided all of the following are true:
|
||||
- `F0` and `F1` have the same number of parameters, and each parameter `D0n` in `F0` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter `D1n` in `F1`.
|
||||
- For each value parameter (a parameter with no `ref`, `out`, or `in` modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `F0` to the corresponding parameter type in `F1`.
|
||||
- For each `ref`, `out`, or `in` parameter, the parameter type in `F0` is the same as the corresponding parameter type in `F1`.
|
||||
- If the return type is by value (no `ref` or `ref readonly`), an identity, implicit reference, or implicit pointer conversion exists from the return type of `F1` to the return type of `F0`.
|
||||
- If the return type is by reference (`ref` or `ref readonly`), the return type and `ref` modifiers of `F1` are the same as the return type and `ref` modifiers of `F0`.
|
||||
- The calling convention of `F0` is the same as the calling convention of `F1`.
|
||||
|
||||
### Allow address-of to target methods
|
||||
|
||||
Method groups will now be allowed as arguments to an address-of expression. The type of such an
|
||||
|
@ -152,11 +163,27 @@ unsafe class Util {
|
|||
}
|
||||
```
|
||||
|
||||
The conversion of an address-of method group to `delegate*` has roughly the same process as method group to `delegate`
|
||||
conversion. There are two additional restrictions to the existing process:
|
||||
In an unsafe context, a method `M` is compatible with a function pointer type `F` if all of the following are true:
|
||||
- `M` and `F` have the same number of parameters, and each parameter in `D` has the same `ref`, `out`, or `in` modifiers as the corresponding parameter in `F`.
|
||||
- For each value parameter (a parameter with no `ref`, `out`, or `in` modifier), an identity conversion, implicit reference conversion, or implicit pointer conversion exists from the parameter type in `M` to the corresponding parameter type in `F`.
|
||||
- For each `ref`, `out`, or `in` parameter, the parameter type in `M` is the same as the corresponding parameter type in `F`.
|
||||
- If the return type is by value (no `ref` or `ref readonly`), an identity, implicit reference, or implicit pointer conversion exists from the return type of `F` to the return type of `M`.
|
||||
- If the return type is by reference (`ref` or `ref readonly`), the return type and `ref` modifiers of `F` are the same as the return type and `ref` modifiers of `M`.
|
||||
- The calling convention of `M` is the same as the calling convention of `F`.
|
||||
- `M` is a static method.
|
||||
|
||||
- Only members of the method group that are marked as `static` will be considered.
|
||||
- Only a `delegate*` with a managed calling convention can be the target of such a conversion.
|
||||
In an unsafe context, an implicit conversion exists from an address-of expression whose target is a method group `E` to a compatible function pointer type `F` if `E` contains at least one method that is applicable in its normal form to an argument list constructed by use of the parameter types and modifiers of `F`, as described in the following.
|
||||
- A single method `M` is selected corresponding to a method invocation of the form `E(A)` with the following modifications:
|
||||
- The arguments list `A` is a list of expressions, each classified as a variable and with the type and modifier (`ref`, `out`, or `in`) of the corresponding _formal\_parameter\_list_ of `D`.
|
||||
- The candidate methods are only those methods that are applicable in their normal form, not those applicable in their expanded form.
|
||||
- The candidate methods are only those methods that are static.
|
||||
- If the algorithm of Method invocations produces an error, then a compile-time error occurs. Otherwise, the algorithm produces a single best method `M` having the same number of parameters as `F` and the conversion is considered to exist.
|
||||
- The selected method `M` must be compatible (as defined above) with the function pointer type `F`. Otherwise, a compile-time error occurs.
|
||||
- The result of the conversion is a function pointer of type `F`.
|
||||
|
||||
An implicit conversion exists from an address-of expression whose target is a method group `E` to `void*` if there is only one static method `M` in `E`.
|
||||
If there is one static method, then the single best method from `E` is `M`.
|
||||
Otherwise, a compile-time error occurs.
|
||||
|
||||
This means developers can depend on overload resolution rules to work in conjunction with the
|
||||
address-of operator:
|
||||
|
@ -293,7 +320,7 @@ hide the fact that this is a pointer value and it kept peeking through even with
|
|||
the conversion to `object` can't be allowed, it can't be a member of a `class`, etc ... The C# design is to require
|
||||
`unsafe` for all pointer uses and hence this design follows that.
|
||||
|
||||
Developers will still be capable of preventing a _safe_ wrapper on top of `delegate*` values the same way that they do
|
||||
Developers will still be capable of presenting a _safe_ wrapper on top of `delegate*` values the same way that they do
|
||||
for normal pointer types today. Consider:
|
||||
|
||||
``` csharp
|
||||
|
|
37
proposals/local-function-attributes.md
Normal file
37
proposals/local-function-attributes.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Attributes on local functions
|
||||
|
||||
## Attributes
|
||||
|
||||
Local function declarations are now permitted to have [attributes](../spec/attributes.md). Parameters and type parameters on local functions are also allowed to have attributes.
|
||||
|
||||
Attributes with a specified meaning when applied to a method, its parameters, or its type parameters will have the same meaning when applied to a local function, its parameters, or its type parameters, respectively.
|
||||
|
||||
A local function can be made conditional in the same sense as a [conditional method](../spec/attributes.md#the-conditional-attribute) by decorating it with a `[ConditionalAttribute]`. A conditional local function must also be `static`. All restrictions on conditional methods also apply to conditional local functions, including that the return type must be `void`.
|
||||
|
||||
## Extern
|
||||
|
||||
The `extern` modifier is now permitted on local functions. This makes the local function external in the same sense as an [external method](../spec/classes.md#external-methods).
|
||||
|
||||
Similarly to an external method, the *local-function-body* of an external local function must be a semicolon. A semicolon *local-function-body* is only permitted on an external local function.
|
||||
|
||||
An external local function must also be `static`.
|
||||
|
||||
## Syntax
|
||||
|
||||
The [local functions grammar](csharp-7.0/local-functions.md#syntax-grammar) is modified as follows:
|
||||
```
|
||||
local-function-header
|
||||
: attributes? local-function-modifiers? return-type identifier type-parameter-list?
|
||||
( formal-parameter-list? ) type-parameter-constraints-clauses
|
||||
;
|
||||
|
||||
local-function-modifiers
|
||||
: (async | unsafe | static | extern)*
|
||||
;
|
||||
|
||||
local-function-body
|
||||
: block
|
||||
| arrow-expression-body
|
||||
| ';'
|
||||
;
|
||||
```
|
|
@ -61,3 +61,18 @@ For a record struct or a record class:
|
|||
|
||||
* A public get-only auto-property is created. Its value is initialized during construction with the value of the corresponding primary constructor parameter. Each "matching" inherited abstract property's get accessor is overridden.
|
||||
|
||||
### Equality members
|
||||
|
||||
Record types produce synthesized implementations for the following methods:
|
||||
|
||||
* `object.GetHashCode()` override, unless it is sealed or user provided
|
||||
* `object.Equals(object)` override, unless it is sealed or user provided
|
||||
* `T Equals(T)` method, where `T` is the current type
|
||||
|
||||
`T Equals(T)` is specified to perform value equality, comparing the property with same name as
|
||||
each primary constructor parameter to the corresponding property of the other type.
|
||||
`object.Equals` performs the equivalent of
|
||||
|
||||
```C#
|
||||
override Equals(object o) => Equals(o as T);
|
||||
```
|
||||
|
|
|
@ -20,14 +20,17 @@ Dictionary<string, List<int>> field = new() {
|
|||
{ "item1", new() { 1, 2, 3 } }
|
||||
};
|
||||
```
|
||||
|
||||
Allow omitting the type when it can be inferred from usage.
|
||||
```cs
|
||||
XmlReader.Create(reader, new() { IgnoreWhitespace = true });
|
||||
```
|
||||
|
||||
Instantiate an object without spelling out the type.
|
||||
```cs
|
||||
private readonly static object s_syncObj = new();
|
||||
```
|
||||
|
||||
## Detailed design
|
||||
[design]: #detailed-design
|
||||
|
||||
|
@ -38,14 +41,15 @@ object_creation_expression
|
|||
| 'new' type object_or_collection_initializer
|
||||
;
|
||||
```
|
||||
|
||||
A target-typed `new` is convertible to any type. As a result, it does not contribute to overload resolution. This is mainly to avoid unpredictable breaking changes.
|
||||
|
||||
The argument list and the initializer expressions will be bound after the type is determined.
|
||||
|
||||
The type of the expression would be inferred from the target-type which would be required to be one of the following:
|
||||
|
||||
- **Any struct type**
|
||||
- **Any reference type**
|
||||
- **Any struct type** (including tuple types)
|
||||
- **Any reference type** (including delegate types)
|
||||
- **Any type parameter** with a constructor or a `struct` constraint
|
||||
|
||||
with the following exceptions:
|
||||
|
@ -53,10 +57,12 @@ with the following exceptions:
|
|||
- **Enum types:** not all enum types contain the constant zero, so it should be desirable to use the explicit enum member.
|
||||
- **Interface types:** this is a niche feature and it should be preferable to explicitly mention the type.
|
||||
- **Array types:** arrays need a special syntax to provide the length.
|
||||
- **Struct default constructor**: this rules out all primitive types and most value types. If you wanted to use the default value of such types you could write `default` instead.
|
||||
- **dynamic:** we don't allow `new dynamic()`, so we don't allow `new()` with `dynamic` as a target type.
|
||||
|
||||
All the other types that are not permitted in the *object_creation_expression* are excluded as well, for instance, pointer types.
|
||||
|
||||
When the target type is a nullable value type, the target-typed `new` will be converted to the underlying type instead of the nullable type.
|
||||
|
||||
> **Open Issue:** should we allow delegates and tuples as the target-type?
|
||||
|
||||
The above rules include delegates (a reference type) and tuples (a struct type). Although both types are constructible, if the type is inferable, an anonymous function or a tuple literal can already be used.
|
||||
|
@ -67,23 +73,20 @@ Action a = new(() => {}); // "new" is redundant
|
|||
(int a, int b) t = new(); // ruled out by "use of struct default constructor"
|
||||
Action a = new(); // no constructor found
|
||||
|
||||
var x = new() == (1, 2); // ruled out by "use of struct default constructor"
|
||||
var x = new(1, 2) == (1, 2) // "new" is redundant
|
||||
```
|
||||
### Miscellaneous
|
||||
|
||||
`throw new()` is disallowed.
|
||||
|
||||
> **Open Issue:** should we allow `throw new()` with `Exception` as the target-type?
|
||||
Target-typed `new` is not allowed with binary operators.
|
||||
|
||||
We have `throw null` today, but not `throw default` (though it would have the same effect). On the other hand, `throw new()` could be actually useful as a shorthand for `throw new Exception(...)`. Note that it is already allowed by the current specification. `Exception` is a reference type, and the specification for the throw statement says that the expression is converted to `Exception`.
|
||||
It is disallowed when there is no type to target: unary operators, collection of a `foreach`, in a `using`, in a deconstruction, in an `await` expression, as an anonymous type property (`new { Prop = new() }`), in a `lock` statement, in a `sizeof`, in a `fixed` statement, in a member access (`new().field`), in a dynamically dispatched operation (`someDynamic.Method(new())`), in a LINQ query, as the operand of the `is` operator, as the left operand of the `??` operator, ...
|
||||
|
||||
> **Open Issue:** should we allow usages of a target-typed `new` with user-defined comparison and arithmetic operators?
|
||||
|
||||
For comparison, `default` only supports equality (user-defined and built-in) operators. Would it make sense to support other operators for `new()` as well?
|
||||
It is also disallowed as a `ref`.
|
||||
|
||||
## Drawbacks
|
||||
[drawbacks]: #drawbacks
|
||||
|
||||
None.
|
||||
There were some concerns with target-typed `new` creating new categories of breaking changes, but we already have that with `null` and `default`, and that has not been a significant problem.
|
||||
|
||||
## Alternatives
|
||||
[alternatives]: #alternatives
|
||||
|
@ -96,6 +99,11 @@ Most of complaints about types being too long to duplicate in field initializati
|
|||
- Should we forbid usages in expression trees? (no)
|
||||
- How the feature interacts with `dynamic` arguments? (no special treatment)
|
||||
- How IntelliSense should work with `new()`? (only when there is a single target-type)
|
||||
|
||||
## Design meetings
|
||||
|
||||
- [LDM-2017-10-18](https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-10-18.md#100)
|
||||
- [LDM-2018-05-21](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-05-21.md)
|
||||
- [LDM-2018-06-25](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-06-25.md)
|
||||
- [LDM-2018-08-22](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-08-22.md#target-typed-new)
|
||||
- [LDM-2018-10-17](https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md)
|
||||
|
|
Loading…
Reference in a new issue