Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Hinweis
Dieser Artikel ist eine Featurespezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.
Es kann einige Abweichungen zwischen der Featurespezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den relevanten Sprachentwurfsbesprechungen (LDM)-Notizen erfasst.
Weitere Informationen zum Einführen von Featurespezifikationen in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.
Champion Issue: https://github.com/dotnet/csharplang/issues/8714
Zusammenfassung
Wir stellen erstklassige Unterstützung für Span<T> und ReadOnlySpan<T> in der Sprache vor, einschließlich neuer impliziter Konvertierungstypen und betrachten sie an weiteren Stellen, was eine natürlichere Programmierung mit diesen integralen Typen ermöglicht.
Motivation
Seit ihrer Einführung in C# 7.2 haben sich Span<T> und ReadOnlySpan<T> auf viele entscheidende Weisen in die Sprache und die Basisklassenbibliothek (BCL) integriert. Dies eignet sich hervorragend für Entwickler, da ihre Einführung die Leistung verbessert, ohne die Entwicklersicherheit zu kosten. Die Sprache hat diese Typen jedoch in einigen wichtigen Punkten auf Abstand gehalten, was es schwierig macht, die Absicht von APIs auszudrücken und zu einer erheblichen Verdoppelung der Oberfläche für neue APIs führt. Beispielsweise hat die BCL eine Reihe neuer Tensor-Grundtyp-APIs in .NET 9 hinzugefügt, aber diese APIs werden alle auf ReadOnlySpan<T> angeboten. C# erkennt die Beziehung zwischen ReadOnlySpan<T>, Span<T> und T[] nicht. Obwohl also benutzerdefinierte Konvertierungen zwischen diesen Typen vorhanden sind, können diese nicht für Empfänger von Erweiterungsmethoden verwendet werden, können nicht mit anderen benutzerdefinierten Konvertierungen kombiniert werden und helfen nicht bei allen Szenarien mit generischer Typinferenz.
Benutzer müssen explizite Konvertierungen oder Typargumente verwenden. Das bedeutet, dass die IDE-Tools die Benutzer nicht zur Verwendung dieser APIs anleiten, da nichts der IDE anzeigt, dass es gültig ist, diese Typen nach der Konvertierung zu übergeben. Um maximale Benutzerfreundlichkeit für diesen API-Stil bereitzustellen, muss die BCL einen ganzen Satz von Span<T>- und T[]-Überladungen definieren, was eine Menge doppelter Funktionsoberfläche ist, die ohne wirklichen Nutzen beibehalten werden muss. Dieser Vorschlag zielt darauf ab, das Problem zu beheben, indem die Sprache diese Typen und Konvertierungen besser erkennt.
Zum Beispiel kann die BCL nur eine Überladung eines MemoryExtensions -Helpers wie:
int[] arr = [1, 2, 3];
Console.WriteLine(
arr.StartsWith(1) // CS8773 in C# 13, permitted with this proposal
);
public static class MemoryExtensions
{
public static bool StartsWith<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T> => span.Length != 0 && EqualityComparer<T>.Default.Equals(span[0], value);
}
Bisher wären Span- und Arrayüberladungen erforderlich, um die Erweiterungsmethode für Span/Array-typierte Variablen nutzbar zu machen, da benutzerdefinierte Konvertierungen (die zwischen Span/Array/ReadOnlySpan vorhanden sind) für Erweiterungsempfänger nicht berücksichtigt werden.
Detailliertes Design
Die Änderungen dieses Vorschlags werden an LangVersion >= 14 gebunden sein.
Spanne Konvertierungen
Wir fügen einen neuen Typ impliziter Konvertierung, eine implizite Bereichs-Konvertierung, der Liste in §10.2.1 hinzu. Diese Konvertierung ist eine Konvertierung vom Typ und ist wie folgt definiert:
Eine implizite Span-Konvertierung erlaubt, array_types, System.Span<T>, System.ReadOnlySpan<T> und string wie folgt ineinander zu konvertieren.
- Von einem eindimensionalen
array_typemit ElementtypEizuSystem.Span<Ei> - Von einem eindimensionalen
array_typemit ElementtypEinachSystem.ReadOnlySpan<Ui>, vorausgesetzt, dassEikovarianz-konvertierbar ist (§18.2.3.3) zuUi. - Von
System.Span<Ti>bisSystem.ReadOnlySpan<Ui>, vorausgesetzt, dassTikovarianzkonvertierbar (§18.2.3.3) zuUi - Von
System.ReadOnlySpan<Ti>bisSystem.ReadOnlySpan<Ui>, vorausgesetzt, dassTikovarianzkonvertierbar (§18.2.3.3) zuUi - Von
stringbisSystem.ReadOnlySpan<char>
Alle Span/ReadOnlySpan-Typen werden für die Konvertierung als anwendbar betrachtet, wenn sie ref structs sind und mit ihrem vollqualifizierten Namen übereinstimmen (LDM 2024-06-24).
Außerdem fügen wir die implizite Span-Konvertierung zur Liste der impliziten Standardkonvertierungen hinzu (§10.4.2). Dadurch kann die Überladungsauflösung sie bei der Argumentauflösung berücksichtigen, wie in dem zuvor verlinkten API-Vorschlag.
Die expliziten Spannenumwandlungen sind die folgenden:
- Alle impliziten Span-Konvertierungen.
- Von einem array_type mit Elementtyp
TizuSystem.Span<Ui>oderSystem.ReadOnlySpan<Ui>, vorausgesetzt, dass eine explizite Verweiskonvertierung vonTizuUiexistiert.
Es gibt keine standardmäßige explizite Span-Konvertierung im Gegensatz zu anderen expliziten Standardkonvertierungen (§10.4.3), die immer aufgrund der entgegengesetzten impliziten Standardkonvertierung vorhanden sind.
Benutzerdefinierte Konvertierungen
Benutzerdefinierte Konvertierungen werden beim Konvertieren zwischen Typen, für die eine implizite oder eine explizite Span-Konvertierung vorhanden ist, nicht berücksichtigt.
Die impliziten Span-Konvertierungen sind von der Regel ausgenommen, dass es nicht möglich ist, einen benutzerdefinierten Operator zwischen Typen zu definieren, für die eine nicht benutzerdefinierte Konvertierung vorhanden ist (§10.5.2 Zulässige benutzerdefinierte Konvertierungen). Dies ist erforderlich, damit die BCL die bestehenden Span-Konvertierungsoperatoren auch nach dem Wechsel zu C# 14 weiter definieren kann (sie werden weiterhin für niedrigere LangVersionen benötigt und auch weil diese Operatoren im Codegen der neuen Standard-Span-Konvertierungen verwendet werden). Es kann jedoch als Implementierungsdetail betrachtet werden (Codegen und niedrigere LangVersions sind nicht Teil der Spezifikation) und Roslyn verletzt diesen Teil der Spezifikation trotzdem (diese spezielle Regel über benutzerdefinierte Konvertierungen wird nicht erzwungen).
Erweiterungsempfänger
Außerdem fügen wir die implizite Span-Konvertierung zur Liste der zulässigen impliziten Konvertierungen für den ersten Parameter einer Erweiterungsmethode hinzu, wenn die Anwendbarkeit (12.8.9.3) bestimmt wird (Änderung in Fettdruck).
Eine Erweiterungsmethode
Cᵢ.Mₑist berechtigt, wenn:
Cᵢist eine nicht generische, nicht geschachtelte Klasse- Der Name von
Mₑlautet identifierMₑist zugriffsfrei und anwendbar, wenn es auf die Argumente als statische Methode angewendet wird, wie oben gezeigt.- Es existiert eine implizite Identität, Referenz
oder Boxing, Boxing oder span Umwandlung von expr in den Typ des ersten Parameters vonMₑ. Span-Konvertierung wird nicht berücksichtigt, wenn eine Überlastauflösung für eine Methodengruppen-Konvertierung durchgeführt wird.
Beachten Sie, dass die implizite Span-Konvertierung für erweiterungsempfänger in Methodengruppenkonvertierungen (LDM 2024-07-15) nicht berücksichtigt wird, wodurch der folgende Code weiterhin funktioniert, anstatt zu einem Kompilierungszeitfehler CS1113: Extension method 'E.M<int>(Span<int>, int)' defined on value type 'Span<int>' cannot be used to create delegateszu führen:
using System;
using System.Collections.Generic;
Action<int> a = new int[0].M; // binds to M<int>(IEnumerable<int>, int)
static class E
{
public static void M<T>(this Span<T> s, T x) => Console.Write(1);
public static void M<T>(this IEnumerable<T> e, T x) => Console.Write(2);
}
Als mögliche zukünftige Arbeit könnten wir in Erwägung ziehen, diese Bedingung zu entfernen, die besagt, dass die Span-Konvertierung für den Erweiterungsempfänger in Methodengruppen-Konvertierungen nicht berücksichtigt wird, und stattdessen Änderungen zu implementieren, so dass das Szenario wie das obige am Ende erfolgreich die Span Überladung aufruft:
- Der Compiler könnte einen Thunk ausgeben, der das Array als Empfänger übernimmt und die Span-Konvertierung intern ausführt (ähnlich wie der Benutzer, der den Delegaten
x => new int[0].M(x)manuell erstellt). - Wertdelegierte könnten, wenn sie implementiert sind,
Spandirekt als Empfänger nehmen.
Abweichung
Das Ziel des Varianzabschnitts in der impliziten Span-Konvertierung ist die Replikation einiger Kovarianz für System.ReadOnlySpan<T>. Laufzeitänderungen wären erforderlich, um die Varianz durch Generika hier vollständig zu implementieren (siehe .. /csharp-13.0/ref-struct-interfaces.md für die Verwendung von ref struct Typen in Generika), aber wir können eine begrenzte Anzahl von Kovarianzen durch Verwendung einer vorgeschlagenen .NET 9-API zulassen: https://github.com/dotnet/runtime/issues/96952. Dadurch kann die Sprache System.ReadOnlySpan<T> so behandeln, als ob T als out T in bestimmten Szenarien deklariert wäre. Wir untersuchen diese Variantenumwandlung jedoch nicht gründlich durch alle Varianzszenarien und fügen sie nicht zur Definition von varianzkonvertibel in §18.2.3.3 hinzu. Wenn wir in Zukunft die Laufzeit ändern, um die Abweichung hier besser zu verstehen, können wir die geringfügige Änderung vornehmen, um sie in der Sprache vollständig zu erkennen.
Muster
Beachten Sie, dass bei der Verwendung von ref struct als Typ in einem beliebigen Muster nur Identitätsumwandlungen zulässig sind.
class C<T> where T : allows ref struct
{
void M1(T t) { if (t is T x) { } } // ok (T is T)
void M2(R r) { if (r is R x) { } } // ok (R is R)
void M3(T t) { if (t is R x) { } } // error (T is R)
void M4(R r) { if (r is T x) { } } // error (R is T)
}
ref struct R { }
Aus der Spezifikation des is-type-Operators (§12.12.12.1):
Das Ergebnis des Vorgangs
E is T[...] ist ein boolescher Wert, der angibt, obEnicht null ist und erfolgreich durch eine Verweiskonvertierung, eine Boxing-Konvertierung, eine Unboxing-Konvertierung, eine Einhüllungskonvertierung oder eine Auspackkonvertierung in den TypTkonvertiert werden kann.[...]
Wenn
Tein nicht-nullbarer Wertetyp ist, ist das Ergebnistrue, wennDundTder gleiche Typ sind.
Dieses Verhalten ändert sich nicht mit diesem Feature, daher ist es nicht möglich, Muster für Span/ReadOnlySpanzu schreiben, obwohl ähnliche Muster für Arrays (einschließlich Varianz) möglich sind:
using System;
M1<object[]>(["0"]); // prints
M1<string[]>(["1"]); // prints
void M1<T>(T t)
{
if (t is object[] r) Console.WriteLine(r[0]); // ok
}
void M2<T>(T t) where T : allows ref struct
{
if (t is ReadOnlySpan<object> r) Console.WriteLine(r[0]); // error
}
Codeerzeugung
Die Konvertierungen sind immer vorhanden, unabhängig davon, ob Laufzeithelfer vorhanden sind, die zu ihrer Implementierung verwendet wurden (LDM 2024-05-13). Wenn die Helfer nicht vorhanden sind, führt der Versuch, die Konvertierung zu verwenden, zu einem Kompilierfehler, dass ein vom Compiler erforderliches Element fehlt.
Der Compiler erwartet, dass die folgenden Hilfsprogramme oder Entsprechungen zum Implementieren der Konvertierungen verwendet werden:
| Umwandlung | Helfer |
|---|---|
| Array zu Span |
static implicit operator Span<T>(T[]) (definiert in Span<T>) |
| Array zu ReadOnlySpan |
static implicit operator ReadOnlySpan<T>(T[]) (definiert in ReadOnlySpan<T>) |
| Span zu ReadOnlySpan |
static implicit operator ReadOnlySpan<T>(Span<T>) (definiert in Span<T>) und static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| ReadOnlySpan zu ReadOnlySpan | static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| string zu ReadOnlySpan | static ReadOnlySpan<char> MemoryExtensions.AsSpan(string) |
Beachten Sie, dass MemoryExtensions.AsSpan anstelle des entsprechenden impliziten Operators verwendet wird, der auf string definiert ist.
Dies bedeutet, dass sich der Codegen zwischen LangVersions unterscheidet (der implizite Operator wird in C# 13 verwendet; die statische Methode AsSpan wird in C# 14 verwendet).
Andererseits kann die Konvertierung auf .NET Framework ausgegeben werden (die AsSpan Methode ist dort vorhanden, während der string Operator nicht).
Die explizite Konvertierung von Array in (ReadOnly)Span konvertiert zunächst explizit vom Quell-Array in ein Array mit dem Typ des Zielelements und dann in (ReadOnly)Span über denselben Helfer, den auch eine implizite Konvertierung verwenden würde, d.h. das entsprechende op_Implicit(T[]).
Bessere Konvertierung eines Ausdrucks
Bessere Konvertierung von Ausdruck (§12.6.4.5) wurde aktualisiert, um implizite span-Konvertierungen zu bevorzugen. Dies basiert auf Änderungen bei der Auflösung von Überladungen in Sammlungsausdrücken.
Gegeben eine implizite Konvertierung
C₁, die von einem AusdruckEzu einem TypT₁konvertiert, und eine implizite KonvertierungC₂, die von einem AusdruckEzu einem TypT₂konvertiert, istC₁eine bessere Konvertierung alsC₂, wenn eine der folgenden Bedingungen erfüllt ist:
Eist ein Sammlungsausdruck, undC₁ist eine bessere Sammlungsumwandlung von Ausdruck alsC₂Eist kein Auflistungsausdruck und eines der folgenden trifft zu:
Estimmt genau mitT₁überein, undEstimmt nicht genau mitT₂Eentspricht wederT₁nochT₂, undC₁ist eine implizite Span-Konvertierung, währendC₂keine implizite Span-Konvertierung ist.Estimmt genau mit beiden oder keinem vonT₁undT₂überein, beide oder keiner vonC₁undC₂sind eine implizite Spannenumwandlung, undT₁ist ein besseres Umwandlungsziel alsT₂Eist eine Methodengruppe,T₁ist mit der einzigen besten Methode aus der Methodengruppe für die KonvertierungC₁kompatibel undT₂ist nicht mit der einzigen besten Methode aus der Methodengruppe für die Konvertierung kompatibel.C₂
Besseres Konvertierungsziel
Besseres Konvertierungsziel (§12.6.4.7) wird aktualisiert, um ReadOnlySpan<T> gegenüber Span<T> zu bevorzugen.
Bei den beiden Typen
T₁undT₂istT₁ein besseres Konvertierungsziel alsT₂, wenn eine der folgenden Bedingungen erfüllt ist:
T₁istSystem.ReadOnlySpan<E₁>,T₂istSystem.Span<E₂>, und eine Identitätskonvertierung vonE₁zuE₂existiertT₁istSystem.ReadOnlySpan<E₁>,T₂istSystem.ReadOnlySpan<E₂>, und eine implizite Konvertierung vonT₁zuT₂ist vorhanden, während keine implizite Konvertierung vonT₂zuT₁vorhanden ist- Mindestens eine von
T₁oderT₂ist nichtSystem.ReadOnlySpan<Eᵢ>und ist nichtSystem.Span<Eᵢ>, und eine implizite Konvertierung vonT₁zuT₂existiert und keine implizite Konvertierung vonT₂zuT₁existiert.- ...
Design-Besprechungen:
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#preferring-readonlyspant-over-spant-conversions
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-09.md#first-class-span-open-questions
Hinweise zur Verbesserung
Die Regel Bessere Konvertierung von Ausdruck sollte sicherstellen, dass immer dann, wenn eine Überladung aufgrund der neuen Span-Konvertierungen anwendbar wird, jede potenzielle Mehrdeutigkeit mit einer anderen Überladung vermieden wird, weil die neu anwendbare Überladung bevorzugt wird.
Ohne diese Regel würde der folgende Code, der erfolgreich in C# 13 kompiliert wurde, zu einem Mehrdeutigkeitsfehler in C# 14 führen, da die neue implizite Standardkonvertierung von Array zu ReadOnlySpan auf einen Erweiterungsmethodenempfänger anwendbar ist:
using System;
using System.Collections.Generic;
var a = new int[] { 1, 2, 3 };
a.M();
static class E
{
public static void M(this IEnumerable<int> x) { }
public static void M(this ReadOnlySpan<int> x) { }
}
Die Regel ermöglicht auch die Einführung neuer APIs, die zuvor zu Mehrdeutigkeiten führen würden, z. B.:
using System;
using System.Collections.Generic;
C.M(new int[] { 1, 2, 3 }); // would be ambiguous before
static class C
{
public static void M(IEnumerable<int> x) { }
public static void M(ReadOnlySpan<int> x) { } // can be added now
}
Warnung
Da die Besserkeitsregel für die Span-Konvertierungen definiert ist, die nur in LangVersion >= 14existieren, können die API-Autoren keine neuen Überladungen hinzufügen, wenn sie weiterhin Benutzer auf LangVersion <= 13unterstützen möchten.
Wenn z.B. .NET 9 BCL solche Überladungen einführt, erhalten Benutzer, die auf net9.0 TFM aktualisieren, aber auf der niedrigeren LangVersion bleiben, Mehrdeutigkeitsfehler für bestehenden Code.
Siehe auch eine offene Frage weiter unten.
Typinferenz
Wir aktualisieren den Abschnitt über Typinferenzen in der Spezifikation wie folgt (Änderungen in bold).
12.6.3.9 Exakte Ableitungen
Eine genaue Ableitungaus einem Typ
Uin einen TypVerfolgt wie folgt:
- Wenn
Veiner der nicht korrigiertenXᵢist, wirdUdem Satz exakter Grenzen fürXᵢhinzugefügt.- Alternativ werden
V₁...VₑundU₁...Uₑbestimmt, indem überprüft wird, ob einer der folgenden Fälle zutrifft.
Vist ein ArraytypV₁[...]undUist ein ArraytypU₁[...]desselben Rangs.Vist einSpan<V₁>undUist ein ArraytypU₁[]oder einSpan<U₁>Vist einReadOnlySpan<V₁>undUist entweder ein ArraytypU₁[]oder einSpan<U₁>oder einReadOnlySpan<U₁>Vist der TypV₁?undUist der TypU₁Vist ein konstruierter TypC<V₁...Vₑ>undUist ein konstruierter TypC<U₁...Uₑ>
Wenn eines dieser Fälle zutrifft, wird eine genaue Ableitung von jedemUᵢauf die entsprechendeVᵢvorgenommen.- Andernfalls werden keine Ableitungen vorgenommen.
12.6.3.10 Untergrenzableitungen
Eine Untergrenzableitung von einem Typ
Uin einen TypVwird wie folgt vorgenommen:
- Wenn
Veiner der nicht korrigiertenXᵢist, wirdUdem Satz unterer Grenzen fürXᵢhinzugefügt.- Andernfalls, wenn
Vder TypV₁?undUder TypU₁?sind, wird eine Untergrenzenableitung vonU₁inV₁vorgenommen.- Alternativ werden
U₁...UₑundV₁...Vₑbestimmt, indem überprüft wird, ob einer der folgenden Fälle zutrifft.
Vist ein ArraytypV₁[...]undUein ArraytypU₁[...]desselben Rangs.Vist einSpan<V₁>undUist ein ArraytypU₁[]oder einSpan<U₁>Vist einReadOnlySpan<V₁>undUist entweder ein ArraytypU₁[]oder einSpan<U₁>oder einReadOnlySpan<U₁>Vist einer vonIEnumerable<V₁>,ICollection<V₁>,IReadOnlyList<V₁>>,IReadOnlyCollection<V₁>oderIList<V₁>undUist ein eindimensionales Array vom TypU₁[]Vist ein konstruierterclass-,struct-,interface- oderdelegate-TypC<V₁...Vₑ>, und es gibt einen eindeutigen TypC<U₁...Uₑ>, sodass für dasU-Element (oder, wennUvon Typparameterist, seine effektive Basisklasse oder ein Element seines effektiven Schnittstellensatzes) Folgendes gilt: Es ist identisch mit, erbt (inherits) (direkt oder indirekt) von oder implementiert (direkt oder indirekt)C<U₁...Uₑ>.- (Die Einschränkung der „Eindeutigkeit“ bedeutet, dass im Fall der Schnittstelle
C<T>{} class U: C<X>, C<Y>{}keine Ableitung vonUnachC<T>vorgenommen wird, daU₁auchXoderYsein könnte).
Wenn einer dieser Fälle zutrifft, wird wie folgt ein Rückschluss von jedemUᵢ-Element in das entsprechendeVᵢ-Element durchgeführt:- Wenn
Uᵢnicht als Referenztyp bekannt ist, wird eine exakte Ableitung vorgenommen.- Andernfalls, wenn
Uein Array-Typ ist, wirdeine Untergrenzen-Inferenz gemachtDie Inferenz hängt vom Typ vonVab:
- Wenn
VeinSpan<Vᵢ>ist, wird eine genaue Ableitung vorgenommen- Wenn
Vein Arraytyp oder einReadOnlySpan<Vᵢ>ist, wird eine Untergrenzenableitung vorgenommen.- Andernfalls, wenn
UeinSpan<Uᵢ>ist, hängt die Inferenz vom Typ vonVab:
- Wenn
VeinSpan<Vᵢ>ist, wird eine genaue Ableitung vorgenommen- Wenn
VeinReadOnlySpan<Vᵢ>ist, wird eine untere gebundene Ableitung vorgenommen.- Andernfalls, wenn
UeinReadOnlySpan<Uᵢ>ist undVeinReadOnlySpan<Vᵢ>ist, wird eine untere Schrankenableitung vorgenommen:- Andernfalls, wenn
VC<V₁...Vₑ>ist, hängt die Ableitung von demi-th-Typparameter vonCab:
- Wenn der Typparameter kovariant ist, erfolgt eine Untergrenzenableitung.
- Wenn er kontravariant ist, wird eine Obergrenzenableitung vorgenommen.
- Wenn er invariant ist, wird eine genaue Ableitung vorgenommen.
- Andernfalls werden keine Ableitungen vorgenommen.
Es gibt keine Regeln für die obergebundene Ableitung , da es nicht möglich wäre, sie zu treffen.
Die Typinferenz beginnt nie mit einer Obergrenze, sondern muss durch eine Untergrenze und einen kontravarianten Typparameter gehen.
Aufgrund der Regel "wenn Uᵢ kein Bezugstyp bekannt ist, wird eine genaue Ableitung vorgenommen", konnte das Quelltypargument nicht sein Span/ReadOnlySpan (diese können nicht referenziert werden).
Die Inferenz für die obere Grenze der Spanne würde jedoch nur gelten, wenn der Quelltyp ein Span/ReadOnlySpanwäre, da es Regeln wie:
Uist einSpan<U₁>undVist ein ArraytypV₁[]oder einSpan<V₁>Uist einReadOnlySpan<U₁>undVist entweder ein ArraytypV₁[]oder einSpan<V₁>oder einReadOnlySpan<V₁>
Bahnbrechende Änderungen
Wie jeder Vorschlag, der Umwandlungen bestehender Szenarien verändert, führt dieser Vorschlag einige neue bahnbrechende Änderungen ein. Hier einige Beispiele:
Aufruf von Reverse für ein Array
Das Aufrufen x.Reverse(), bei dem x eine Instanz des Typs T[] ist, würde zuvor an IEnumerable<T> Enumerable.Reverse<T>(this IEnumerable<T>) binden, während es jetzt an void MemoryExtensions.Reverse<T>(this Span<T>) bindet.
Leider sind diese APIs nicht kompatibel (letzteres führt die Umkehrung an Ort und Stelle durch und gibt void zurück).
.NET 10 entschärft dies durch Hinzufügen einer arrayspezifischen Überladung IEnumerable<T> Reverse<T>(this T[]), siehe https://github.com/dotnet/runtime/issues/107723.
void M(int[] a)
{
foreach (var x in a.Reverse()) { } // fine previously, an error now (`Reverse` returns `void`)
foreach (var x in Enumerable.Reverse(a)) { } // workaround
}
Siehe auch:
- https://developercommunity.visualstudio.com/t/Extension-method-SystemLinqEnumerable/10790323
- https://developercommunity.visualstudio.com/t/Compilation-Error-When-Calling-Reverse/10818048
- https://developercommunity.visualstudio.com/t/Version-17131-has-an-obvious-defect-th/10858254
- https://developercommunity.visualstudio.com/t/Visual-Studio-2022-update-breaks-build-w/10856758
- https://github.com/dotnet/runtime/issues/111532
- https://developercommunity.visualstudio.com/t/Backward-compatibility-issue-:-IEnumerab/10896189#T-ND10896782
Design-Besprechung: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse
Zweideutigkeiten
Die folgenden Beispiele schlugen zuvor bei der Typinferenz für die Überladung Span fehl, aber jetzt ist die Typinferenz von array zu Span erfolgreich, daher sind sie mehrdeutig.
Um dies zu umgehen, können Benutzer verwenden .AsSpan() oder API-Autoren verwenden OverloadResolutionPriorityAttribute.
var x = new long[] { 1 };
Assert.Equal([2], x); // previously Assert.Equal<T>(T[], T[]), now ambiguous with Assert.Equal<T>(ReadOnlySpan<T>, Span<T>)
Assert.Equal([2], x.AsSpan()); // workaround
var x = new int[] { 1, 2 };
var s = new ArraySegment<int>(x, 1, 1);
Assert.Equal(x, s); // previously Assert.Equal<T>(T, T), now ambiguous with Assert.Equal<T>(Span<T>, Span<T>)
Assert.Equal(x.AsSpan(), s); // workaround
xUnit fügt weitere Überladungen hinzu, um dies zu vermeiden: https://github.com/xunit/xunit/discussions/3021.
Design-Besprechung: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#new-ambiguities
Kovariante Arrays
Überladungen mit IEnumerable<T> funktionierten bei kovarianten Arrays, aber Überladungen mit Span<T> (die wir jetzt bevorzugen) funktionieren nicht, da die Span-Konvertierung bei kovarianten Arrays eine ArrayTypeMismatchException auslöst.
Man könnte sagen, dass die Span<T> Überladung nicht existieren sollte; stattdessen sollte ReadOnlySpan<T> verwendet werden.
Um dies zu umgehen, können Benutzer .AsEnumerable()verwenden, oder API-Autoren können OverloadResolutionPriorityAttribute verwenden oder eine ReadOnlySpan<T> Überladung hinzufügen, die aufgrund der Besserungsregelbevorzugt wird.
string[] s = new[] { "a" };
object[] o = s;
C.R(o); // wrote 1 previously, now crashes in Span<T> constructor with ArrayTypeMismatchException
C.R(o.AsEnumerable()); // workaround
static class C
{
public static void R<T>(IEnumerable<T> e) => Console.Write(1);
public static void R<T>(Span<T> s) => Console.Write(2);
// another workaround:
public static void R<T>(ReadOnlySpan<T> s) => Console.Write(3);
}
Design-Besprechung: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#covariant-arrays
ReadOnlySpan gegenüber Span bevorzugen
Die Besserungsregel bewirkt, dass ReadOnlySpan-Überladungen gegenüber Span-Überladungen bevorzugt werden, um ArrayTypeMismatchExceptions in kovarianten Array-Szenarienzu vermeiden.
Dies kann in einigen Szenarien zu Kompilierungsumbrüchen führen, z. B. wenn sich die Überladungen je nach Rückgabetyp unterscheiden:
double[] x = new double[0];
Span<ulong> y = MemoryMarshal.Cast<double, ulong>(x); // previously worked, now a compilation error (returns ReadOnlySpan, not Span)
Span<ulong> z = MemoryMarshal.Cast<double, ulong>(x.AsSpan()); // workaround
static class MemoryMarshal
{
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span) => default;
public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span) => default;
}
Siehe https://github.com/dotnet/roslyn/issues/76443.
Ausdrucksbaumstrukturen
Überladungen mit Spannen wie MemoryExtensions.Contains werden gegenüber klassischen Überladungen wie Enumerable.Containsbevorzugt, auch innerhalb von Ausdrucksbäumen - aber ref structs werden von der Interpreter-Engine nicht unterstützt:
Expression<Func<int[], int, bool>> exp = (array, num) => array.Contains(num);
exp.Compile(preferInterpretation: true); // fails at runtime in C# 14
Expression<Func<int[], int, bool>> exp2 = (array, num) => Enumerable.Contains(array, num); // workaround
exp2.Compile(preferInterpretation: true); // ok
Ebenso müssen Übersetzungsmodule wie LINQ-to-SQL darauf reagieren, wenn ihre Baumbesucher Enumerable.Contains erwarten, weil sie stattdessen MemoryExtensions.Contains vorfinden werden.
Siehe auch:
- https://github.com/dotnet/runtime/issues/109757
- https://github.com/dotnet/docs/issues/43952
- https://github.com/dotnet/efcore/issues/35100
- https://github.com/dotnet/csharplang/discussions/8959
Design-Besprechungen:
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#conversions-in-expression-trees
- https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-06.md#ignoring-ref-structs-in-expressions
Benutzerdefinierte Konvertierungen durch Vererbung
Durch Hinzufügen impliziter Span-Konvertierungen zur Liste der impliziten Standardkonvertierungen können wir das Verhalten möglicherweise ändern, wenn benutzerdefinierte Konvertierungen an einer Typhierarchie beteiligt sind. Dieses Beispiel zeigt diese Änderung im Vergleich zu einem Integer-Szenario, das sich bereits so verhält wie das neue C# 14-Verhalten.
Span<string> span = [];
var d = new Derived();
d.M(span); // Base today, Derived tomorrow
int i = 1;
d.M(i); // Derived today, demonstrates new behavior
class Base
{
public void M(Span<string> s)
{
Console.WriteLine("Base");
}
public void M(int i)
{
Console.WriteLine("Base");
}
}
class Derived : Base
{
public static implicit operator Derived(ReadOnlySpan<string> r) => new Derived();
public static implicit operator Derived(long l) => new Derived();
public void M(Derived s)
{
Console.WriteLine("Derived");
}
}
Weitere Informationen: https://github.com/dotnet/roslyn/issues/78314
Nachschlagen von Erweiterungsmethoden
Durch das Zulassen von impliziten Span-Konvertierungen bei der Suche nach Erweiterungsmethoden können wir potenziell ändern, welche Erweiterungsmethode durch Überladungsauflösung ermittelt wird.
namespace N1
{
using N2;
public class C
{
public static void M()
{
Span<string> span = new string[0];
span.Test(); // Prints N2 today, N1 tomorrow
}
}
public static class N1Ext
{
public static void Test(this ReadOnlySpan<string> span)
{
Console.WriteLine("N1");
}
}
}
namespace N2
{
public static class N2Ext
{
public static void Test(this Span<string> span)
{
Console.WriteLine("N2");
}
}
}
Offene Fragen
Uneingeschränkte Besserkeitsregel
Sollten wir die Besserheitsregel bedingungslos auf LangVersion anwenden? Dadurch könnten API-Autoren neue Span-APIs hinzufügen, bei denen IEnumerable-Entsprechungen vorhanden sind, ohne Benutzer in älteren LangVersions oder anderen Compilern oder Sprachen (z. B. VB) zu unterbrechen. Das würde jedoch bedeuten, dass Benutzer nach dem Aktualisieren des Toolsets ein anderes Verhalten erhalten könnten (ohne LangVersion oder TargetFramework zu ändern):
- Compiler könnte unterschiedliche Überladungen auswählen (technisch eine bahnbrechende Änderung, aber hoffentlich würden diese Überladungen ein gleichwertiges Verhalten haben).
- Es könnten weitere Pausen entstehen, die zu diesem Zeitpunkt noch nicht bekannt sind.
Beachten Sie, dass OverloadResolutionPriorityAttribute dies nicht vollständig gelöst werden kann, da es auch bei älteren LangVersions ignoriert wird.
Es sollte jedoch möglich sein, es zu verwenden, um Mehrdeutigkeiten von VB zu vermeiden, in denen das Attribut erkannt werden sollte.
Ignorieren von weiteren benutzerdefinierten Konvertierungen
Wir haben eine Reihe von Typpaaren definiert, für die sprachdefinierte implizite und explizite Span-Konvertierungen vorhanden sind.
Immer wenn eine sprachdefinierte Spannen-Konvertierung von T1 zu T2 existiert, wird jede benutzerdefinierte Konvertierung von T1 zu T2 ignoriert (unabhängig davon, ob die Spannen- und benutzerdefinierte Konvertierung implizit oder explizit sind).
Beachten Sie, dass dies alle Bedingungen enthält. Zum Beispiel gibt es keine Bereichskonvertierung von Span<object> zu ReadOnlySpan<string> (es gibt eine Konvertierung von Span<T> zu ReadOnlySpan<U>, aber es muss gelten, dass T : U). Eine benutzerdefinierte Konvertierung würde zwischen diesen Typen in Betracht gezogen werden, wenn sie existierte. Dies müsste eine spezialisierte Konvertierung wie von Span<T> zu ReadOnlySpan<string> sein, da Konvertierungsoperatoren keine generischen Parameter haben können.
Sollten benutzerdefinierte Konvertierungen auch zwischen anderen Kombinationen von Array/Span/ReadOnlySpan/String-Typen ignoriert werden, bei denen keine entsprechende sprachdefinierte Span-Konvertierung vorhanden ist?
Beispielsweise, wenn es eine benutzerdefinierte Konvertierung von ReadOnlySpan<T> zu Span<T> gibt, sollten wir sie ignorieren?
Spezifische Möglichkeiten, die Sie berücksichtigen sollten:
-
Wann immer eine Span-Konvertierung von
T1zuT2vorhanden ist, ignorieren Sie jede benutzerdefinierte Konvertierung vonT1zuT2oder vonT2zuT1. -
Benutzerdefinierte Konvertierungen werden beim Konvertieren zwischen verschiedenen Datentypen nicht berücksichtigt.
- jede eindimensionale
array_typeundSystem.Span<T>/System.ReadOnlySpan<T>, - beliebige Kombination von
System.Span<T>/System.ReadOnlySpan<T>, -
stringundSystem.ReadOnlySpan<char>.
- jede eindimensionale
- Wie oben, aber ersetzen Sie den letzten Aufzählungspunkt durch:
-
stringundSystem.Span<char>/System.ReadOnlySpan<char>.
-
- Wie oben, aber ersetzen Sie den letzten Aufzählungspunkt durch:
-
stringundSystem.Span<T>/System.ReadOnlySpan<T>.
-
Technisch lässt die Spezifikation nicht zu, dass einige dieser benutzerdefinierten Konvertierungen sogar definiert werden: Es ist nicht möglich, einen benutzerdefinierten Operator zwischen Typen zu definieren, für die eine nicht benutzerdefinierte Konvertierung vorhanden ist (§10.5.2).
Aber Roslyn verletzt absichtlich diesen Teil der Spezifikation. Und einige Konvertierungen wie zwischen Span und string sind trotzdem zulässig (keine sprachdefinierte Konvertierung zwischen diesen Typen vorhanden).
Dennoch könnten wir alternativ zur ignorierung der Konvertierungen verhindern , dass sie überhaupt definiert werden und vielleicht die Spezifikationsverletzung zumindest für diese neuen Span-Konvertierungen aufheben, d.h. Roslyn so ändern, dass es einen Kompilierzeitfehler meldet, wenn diese Konvertierungen definiert sind (wahrscheinlich mit Ausnahme derer, die bereits in der BCL definiert sind).
Alternativen
Behalten Sie die Dinge so, wie sie sind.
C# feature specifications