Merge remote-tracking branch 'refs/remotes/origin/master' into design-notes
This commit is contained in:
commit
f9cf91154d
|
@ -88,3 +88,14 @@ Features Added in C# Language Versions
|
|||
- [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.
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
# Auto-Implemented Property Field-Targeted Attributes
|
||||
|
||||
* [x] Proposed
|
||||
* [ ] Prototype: Not Started
|
||||
* [ ] Implementation: Not Started
|
||||
* [ ] Specification: Not Started
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
# Unmanaged type constraint
|
||||
|
||||
* [x] Proposed
|
||||
* [ ] Prototype
|
||||
* [ ] Implementation
|
||||
* [ ] Specification
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
# expression variables in initializers
|
||||
|
||||
* [x] Proposed
|
||||
* [ ] Prototype: None
|
||||
* [ ] Implementation: None
|
||||
* [ ] Specification: See below
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
# Improved overload candidates
|
||||
|
||||
* [x] Proposed
|
||||
* [x] Prototype
|
||||
* [x] Implementation
|
||||
* [ ] Specification
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
# Pattern-based `fixed` statement
|
||||
|
||||
* [x] Proposed
|
||||
* [ ] Prototype: Not Started
|
||||
* [ ] Implementation: Not Started
|
||||
* [ ] Specification: Not Started
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
|
@ -33,9 +28,9 @@ It would be desirable to be able to switch to something more flexible that decou
|
|||
[design]: #detailed-design
|
||||
|
||||
## *Pattern* ##
|
||||
A viable pattern-based “fixed” need to:
|
||||
A viable pattern-based “fixed” need to:
|
||||
- Provide the managed references to pin the instance and to initialize the pointer (preferably this is the same reference)
|
||||
- Convey unambiguously the type of the unmanaged element (i.e. “char” for “string”)
|
||||
- Convey unambiguously the type of the unmanaged element (i.e. “char” for “string”)
|
||||
- Prescribe the behavior in "empty" case when there is nothing to refer to.
|
||||
- Should not push API authors toward design decisions that hurt the use of the type outside of `fixed`.
|
||||
|
||||
|
@ -49,8 +44,8 @@ In order to be used by the `fixed` statement the following conditions must be me
|
|||
(`readonly` is permitted so that authors of immutable/readonly types could implement the pattern without adding writeable API that could be used in safe code)
|
||||
1) T is an unmanaged type.
|
||||
(since `T*` becomes the pointer type. The restriction will naturally expand if/when the notion of "unmanaged" is expanded)
|
||||
1) Returns managed `nullptr` when there is no data to pin – probably the cheapest way to convey emptiness.
|
||||
(note that “” string returns a ref to '\0' since strings are null-terminated)
|
||||
1) Returns managed `nullptr` when there is no data to pin – probably the cheapest way to convey emptiness.
|
||||
(note that “” string returns a ref to '\0' since strings are null-terminated)
|
||||
|
||||
Alternatively for the `#3` we can allow the result in empty cases be undefined or implementation-specific.
|
||||
That, however, may make the API more dangerous and prone to abuse and unintended compatibility burdens.
|
||||
|
@ -128,4 +123,4 @@ There is no solution for `System.String` if alternative solution is desired.
|
|||
|
||||
## Design meetings
|
||||
|
||||
None yet.
|
||||
None yet.
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
# Stackalloc array initializers.
|
||||
|
||||
* [x] Proposed
|
||||
* [ ] Prototype: Not Started
|
||||
* [ ] Implementation: Not Started
|
||||
* [ ] Specification: Not Started
|
||||
|
||||
## Summary
|
||||
[summary]: #summary
|
||||
|
||||
|
@ -62,4 +57,4 @@ This is a convenience feature. It is possible to just do nothing.
|
|||
|
||||
## Design meetings
|
||||
|
||||
None yet.
|
||||
None yet.
|
||||
|
|
82
proposals/ranges.cs
Normal file
82
proposals/ranges.cs
Normal file
|
@ -0,0 +1,82 @@
|
|||
namespace System
|
||||
{
|
||||
public readonly struct Index
|
||||
{
|
||||
private readonly int _value;
|
||||
|
||||
public int Value => _value < 0 ? ~_value : _value;
|
||||
public bool FromEnd => _value < 0;
|
||||
|
||||
public Index(int value, bool fromEnd)
|
||||
{
|
||||
if (value < 0) throw new ArgumentException("Index must not be negative.", nameof(value));
|
||||
|
||||
_value = fromEnd ? ~value : value;
|
||||
}
|
||||
|
||||
public static implicit operator Index(int value)
|
||||
=> new Index(value, fromEnd: false);
|
||||
}
|
||||
|
||||
public readonly struct Range
|
||||
{
|
||||
public Index Start { get; }
|
||||
public Index End { get; }
|
||||
|
||||
private Range(Index start, Index end)
|
||||
{
|
||||
this.Start = start;
|
||||
this.End = end;
|
||||
}
|
||||
|
||||
public static Range Create(Index start, Index end) => new Range(start, end);
|
||||
public static Range FromStart(Index start) => new Range(start, new Index(0, fromEnd: true));
|
||||
public static Range ToEnd(Index end) => new Range(new Index(0, fromEnd: false), end);
|
||||
public static Range All() => new Range(new Index(0, fromEnd: false), new Index(0, fromEnd: true));
|
||||
}
|
||||
|
||||
static class Extensions
|
||||
{
|
||||
public static int get_IndexerExtension(this int[] array, Index index) =>
|
||||
index.FromEnd ? array[array.Length - index.Value] : array[index.Value];
|
||||
|
||||
public static int get_IndexerExtension(this Span<int> span, Index index) =>
|
||||
index.FromEnd ? span[span.Length - index.Value] : span[index.Value];
|
||||
|
||||
public static char get_IndexerExtension(this string s, Index index) =>
|
||||
index.FromEnd ? s[s.Length - index.Value] : s[index.Value];
|
||||
|
||||
public static Span<int> get_IndexerExtension(this int[] array, Range range) =>
|
||||
array.Slice(range);
|
||||
|
||||
public static Span<int> get_IndexerExtension(this Span<int> span, Range range) =>
|
||||
span.Slice(range);
|
||||
|
||||
public static string get_IndexerExtension(this string s, Range range) =>
|
||||
s.Substring(range);
|
||||
|
||||
public static Span<T> Slice<T>(this T[] array, Range range)
|
||||
=> array.AsSpan().Slice(range);
|
||||
|
||||
public static Span<T> Slice<T>(this Span<T> span, Range range)
|
||||
{
|
||||
var (start, length) = GetStartAndLength(range, span.Length);
|
||||
return span.Slice(start, length);
|
||||
}
|
||||
|
||||
public static string Substring(this string s, Range range)
|
||||
{
|
||||
var (start, length) = GetStartAndLength(range, s.Length);
|
||||
return s.Substring(start, length);
|
||||
}
|
||||
|
||||
private static (int start, int length) GetStartAndLength(Range range, int entityLength)
|
||||
{
|
||||
var start = range.Start.FromEnd ? entityLength - range.Start.Value : range.Start.Value;
|
||||
var end = range.End.FromEnd ? entityLength - range.End.Value : range.End.Value;
|
||||
var length = end - start;
|
||||
|
||||
return (start, length);
|
||||
}
|
||||
}
|
||||
}
|
60
proposals/ranges.md
Normal file
60
proposals/ranges.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# Ranges
|
||||
|
||||
## Summary
|
||||
|
||||
This feature is about delivering two new operators that allow constructing `System.Index` and `System.Range` objects, and using them to index/slice collections at runtime.
|
||||
|
||||
## Detailed Design
|
||||
|
||||
#### System.Index
|
||||
|
||||
C# has no way of indexing a collection from the end, but rather most indexers use the "from start" notion, or do a "length - i" expression. We introduce a new Index expression that means "from the end". The feature will introduce a new unary prefix "hat" operator. Its single operand must be convertible to `System.Int32`. It will be lowered into the appropriate `System.Index` factory method call.
|
||||
|
||||
```csharp
|
||||
var thirdItem = list[2]; // list[2]
|
||||
var lastItem = list[^1]; // list[Index.CreateFromEnd(1)]
|
||||
|
||||
var multiDimensional = list[3, ^2] // list[3, Index.CreateFromEnd(2)]
|
||||
```
|
||||
|
||||
#### System.Range
|
||||
|
||||
C# has no syntactic way to access "ranges" or "slices" of collections. Usually users are forced to implement complex structures to filter/operate on slices of memory, or resort to LINQ methods like `list.Skip(5).Take(2)`. With the addition of `System.Span<T>` and other similar types, it becomes more important to have this kind of operation supported on a deeper level in the language/runtime, and have the interface unified.
|
||||
|
||||
The language will introduce a new range operator `x..y`. It is a binary infix operator that accepts two expressions. Either operands can be omitted (examples below), and they have to be convertible to `System.Index`. It will be lowered to the appropriate `System.Range` factory method call.
|
||||
|
||||
```csharp
|
||||
var slice1 = list[2..^3]; // list[Range.Create(2, Index.CreateFromEnd(3))]
|
||||
var slice2 = list[..^3]; // list[Range.ToEnd(Index.CreateFromEnd(3))]
|
||||
var slice3 = list[2..]; // list[Range.FromStart(2)]
|
||||
var slice4 = list[..]; // list[Range.All]
|
||||
|
||||
var multiDimensional = list[1..2, ..] // list[Range.Create(1, 2), Range.All]
|
||||
```
|
||||
|
||||
Moreover, `System.Index` should have an implicit conversion from `System.Int32`, in order not to need to overload for mixing integers and indexes over multi-dimensional signatures.
|
||||
|
||||
## Workarounds
|
||||
|
||||
For prototyping reasons, and since runtime/framework collections will not have support for such indexers, the compiler will finally look for the following extension method when doing overload resolution:
|
||||
|
||||
* `op_Indexer_Extension(this TCollection<TItem> collection, ...arguments supplied to the indexer)`
|
||||
|
||||
This workaround will be removed once contract with runtime/framework is finalized, and before the feature is declared complete.
|
||||
|
||||
## Alternatives
|
||||
|
||||
The new operators (`^` and `..`) are syntactic sugar. The functionality can be implemented by explicit calls to `System.Index` and `System.Range` factory methods, but it will result in a lot more boilerplate code, and the experience will be unintuitive.
|
||||
|
||||
## IL Representation
|
||||
|
||||
These two operators will be lowered to regular indexer/method calls, with no change in subsequent compiler layers.
|
||||
|
||||
## Runtime behaviour
|
||||
|
||||
* Compiler can optimize indexers for built-in types like arrays and strings, and lower the indexing to the appropriate existing methods.
|
||||
* `System.Index` will throw if constructed with a negative value.
|
||||
* `^0` does not throw, but it translates to the length of the collection/enumerable it is supplied to.
|
||||
* `Range.All` is semantically equivalent to `0..^0`, and can be deconstructed to these indices.
|
||||
|
||||
## Questions
|
|
@ -376,7 +376,6 @@ static void Main(string[] args) {
|
|||
default:
|
||||
Console.WriteLine("{0} arguments", n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
Loading…
Reference in a new issue