Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Opmerking
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Kampioensprobleem: https://github.com/dotnet/csharplang/issues/8714
Samenvatting
We introduceren eersteklas ondersteuning voor Span<T> en ReadOnlySpan<T> in de taal, waaronder nieuwe impliciete conversietypen en overwegen ze op meer plaatsen, waardoor meer natuurlijke programmering met deze integrale typen mogelijk is.
Motivatie
Sinds hun introductie in versie C# 7.2 hebben Span<T> en ReadOnlySpan<T> zich op veel belangrijke manieren in de taal en in de basisklassebibliotheek (BCL) ingeburgerd. Dit is ideaal voor ontwikkelaars, omdat hun introductie de prestaties verbetert zonder kosten voor de veiligheid van ontwikkelaars. De taal heeft deze typen echter op een aantal belangrijke manieren op afstand gehouden, waardoor het moeilijk is om de intentie van API's uit te drukken en tot een aanzienlijke hoeveelheid duplicatie van oppervlak leidt voor nieuwe API's. De BCL heeft bijvoorbeeld een aantal nieuwe tensor primitieve API's toegevoegd in .NET 9, maar deze API's worden allemaal aangeboden op ReadOnlySpan<T>. C# herkent de relatie tussen ReadOnlySpan<T>, Span<T>en T[]niet, dus ook al zijn er door de gebruiker gedefinieerde conversies tussen deze typen, kunnen ze niet worden gebruikt voor ontvangers van extensiemethoden, kunnen niet worden samengesteld met andere door de gebruiker gedefinieerde conversies en helpen niet bij alle algemene typedeductiescenario's.
Gebruikers moeten expliciete conversies of typeargumenten gebruiken, wat betekent dat IDE-hulpprogramma's gebruikers niet begeleiden bij het gebruik van deze API's, omdat niets aangeeft aan de IDE dat het geldig is om deze typen door te geven na de conversie. Om maximale bruikbaarheid voor deze API-stijl te bieden, moet de BCL een volledige reeks Span<T> en T[] overloads definiëren, wat betekent dat er veel overbodige onderdelen moeten worden onderhouden zonder echt voordeel. Dit voorstel probeert het probleem op te lossen door de taal meer direct deze typen en conversies te erkennen.
De BCL kan bijvoorbeeld slechts één overload van een MemoryExtensions helper toevoegen, zoals:
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);
}
Voorheen zouden span- en matrixoverbelastingen nodig zijn om de extensiemethode bruikbaar te maken voor variabelen met span-/matrixtypen, omdat door de gebruiker gedefinieerde conversies (die bestaan tussen Span/array/ReadOnlySpan) niet worden beschouwd voor extensieontvangers.
Gedetailleerd ontwerp
De wijzigingen in dit voorstel zijn gekoppeld aan LangVersion >= 14.
Omzettingen van tijdsperioden
We voegen een nieuw type impliciete conversie toe aan de lijst in §10.2.1, een impliciete spanconversie. Deze conversie is een conversie van een type en wordt als volgt gedefinieerd:
Met een impliciete spanconversie kunnen array_types, System.Span<T>, System.ReadOnlySpan<T>en string als volgt tussen elkaar worden geconverteerd:
- Van een enkeldimensionale
array_typemet elementtypeEitotSystem.Span<Ei> - Van een enkeldimensionale
array_typemet elementtypeEitotSystem.ReadOnlySpan<Ui>, mitsEicovariantie-converteerbaar is (§18.2.3.3) totUi - Van
System.Span<Ti>totSystem.ReadOnlySpan<Ui>, mitsTicovariantie-converteerbaar is (§18.2.3.3) totUi - Van
System.ReadOnlySpan<Ti>totSystem.ReadOnlySpan<Ui>, mitsTicovariantie-converteerbaar is (§18.2.3.3) totUi - Van
stringtotSystem.ReadOnlySpan<char>
Alle Span-/ReadOnlySpan-typen worden als van toepassing beschouwd voor de conversie als ze ref structzijn en ze overeenkomen met hun volledig gekwalificeerde naam (LDM 2024-06-24).
We voegen ook impliciete spanconversie toe aan de lijst met standaard impliciete conversies (§10.4.2). Hierdoor kan de overbelastingsresolutie rekening houden met de argumentresolutie, zoals in het eerder genoemde API-voorstel.
De expliciete spanningsconversies zijn als volgt:
- Alle impliciete spanconversies.
- Van een array_type met elementtype
TinaarSystem.Span<Ui>ofSystem.ReadOnlySpan<Ui>, op voorwaarde dat er een expliciete verwijzingsconversie bestaat vanTinaarUi.
Er is geen standaard expliciete spanconversie in tegenstelling tot andere standaard expliciete conversies (§10.4.3) die altijd bestaan op basis van de tegenovergestelde impliciete standaardconversie.
Door de gebruiker gedefinieerde conversies
Door de gebruiker gedefinieerde conversies worden niet overwogen bij het converteren tussen typen waarvoor een impliciete of expliciete spanconversie bestaat.
De impliciete spanconversies zijn uitgesloten van de regel dat het niet mogelijk is om een door de gebruiker gedefinieerde operator te definiëren tussen typen waarvoor een niet-door de gebruiker gedefinieerde conversie bestaat (§10.5.2 Toegestane door de gebruiker gedefinieerde conversies). Dit is nodig zodat de BCL de bestaande Span-conversieoperators kan blijven definiëren, zelfs wanneer ze overschakelen naar C# 14 (ze zijn nog steeds nodig voor lagere LangVersions en ook omdat deze operators worden gebruikt in codegen van de nieuwe standaard spanconversies). Maar het kan worden gezien als een implementatiedetail (codegen en lagere LangVersions maken geen deel uit van de specificatie) en Roslyn schendt dit deel van de specificatie toch (deze specifieke regel over door de gebruiker gedefinieerde conversies wordt niet afgedwongen).
Extensieontvanger
We voegen ook impliciete bereikconversie toe aan de lijst met acceptabele impliciete conversies voor de eerste parameter van een extensiemethode bij het bepalen van de toepasbaarheid (12.8.9.3) (vetgedrukte wijziging):
Een extensiemethode
Cᵢ.Mₑkomt in aanmerking als:
Cᵢis een klasse die niet generiek en niet genest is- De naam van
Mₑis identificatorMₑis toegankelijk en van toepassing wanneer deze wordt toegepast op de argumenten als een statische methode, zoals hierboven wordt weergegeven- Een impliciete identiteit, verwijzing
of boksen, boksen of conversie bestaat van expr tot het type van de eerste parameter vanMₑ. Spanconversie wordt niet in overweging genomen wanneer overbelastingsoplossing wordt uitgevoerd voor een conversie van een methodegroep.
Houd er rekening mee dat impliciete spanconversie niet wordt overwogen voor extensieontvanger in methodegroepconversies (LDM 2024-07-15) waardoor de volgende code blijft werken in plaats van dat dit resulteert in een compilatiefout CS1113: Extension method 'E.M<int>(Span<int>, int)' defined on value type 'Span<int>' cannot be used to create delegates:
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 toekomstige mogelijke taak zouden we kunnen overwegen om de voorwaarde te verwijderen waarbij de bereikconversie niet in aanmerking wordt genomen voor een extensieontvanger bij methodegroepconversies. In plaats daarvan zouden we wijzigingen kunnen implementeren, zodat een scenario zoals het bovenstaande succesvol de Span-overbelasting oproept.
- De compiler kan een thunk genereren die de array als ontvanger neemt en de span-conversie binnenin uitvoert (vergelijkbaar met de gebruiker die handmatig de delegate creëert, zoals
x => new int[0].M(x)). - Als ze zijn geïmplementeerd, kunnen waardedelegaten de
Spanrechtstreeks als ontvanger nemen.
Verschil
Het doel van de variantiesectie in impliciete bereikconversie is om een bepaalde hoeveelheid covariantie voor System.ReadOnlySpan<T>te repliceren. Runtimewijzigingen zijn vereist om de variantie volledig te implementeren via generics (zie .. /csharp-13.0/ref-struct-interfaces.md voor het gebruik van ref struct typen in generics), maar we kunnen een beperkte hoeveelheid covariantie toestaan door gebruik te maken van een voorgestelde .NET 9-API: https://github.com/dotnet/runtime/issues/96952. Hierdoor kan de taal System.ReadOnlySpan<T> behandelen alsof de T in sommige scenario's als out T is gedeclareerd. We pluizen deze variantconversie echter niet door alle variantiescenario's en voegen deze niet toe aan de definitie van variantie-converteerbaar in §18.2.3.3. Als we in de toekomst de runtime wijzigen om beter de variatie hier te begrijpen, kunnen we de kleine wijziging die lichte verstoringen veroorzaakt gebruiken om deze volledig in de taal te herkennen.
Patronen
Houd er rekening mee dat wanneer ref structs worden gebruikt als een type in een patroon, alleen identiteitsconversies zijn toegestaan:
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 { }
Uit de specificatie van volgt de is-type-operator (§12.12.12.1):
Het resultaat van de bewerking
E is T[...] is een booleaanse waarde die aangeeft ofEniet-null is en met succes kan worden geconverteerd naar het typeTdoor een verwijzingsconversie, een boxing conversie, een unboxing conversie, een wrapping conversie of een unwrapping conversie.[...]
Als
Teen niet-null-waardetype is, wordt het resultaattruealsDenThetzelfde type zijn.
Dit gedrag verandert niet met deze functie, daarom is het niet mogelijk om patronen te schrijven voor Span/ReadOnlySpan, hoewel vergelijkbare patronen mogelijk zijn voor matrices (inclusief variantie):
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
}
Code genereren
De conversies bestaan altijd, ongeacht of er runtime-helpers aanwezig zijn (LDM 2024-05-13). Als de helpers niet aanwezig zijn, zal een poging tot conversie leiden tot een compilatiefout die meldt dat een vereist lid voor de compiler ontbreekt.
De compiler verwacht de volgende helpers of equivalenten te gebruiken om de conversies te implementeren:
| Conversie | Hulpverleners |
|---|---|
| array naar span |
static implicit operator Span<T>(T[]) (gedefinieerd in Span<T>) |
| matrix naar ReadOnlySpan |
static implicit operator ReadOnlySpan<T>(T[]) (gedefinieerd in ReadOnlySpan<T>) |
| Van Span naar ReadOnlySpan |
static implicit operator ReadOnlySpan<T>(Span<T>) (gedefinieerd in Span<T>) en static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| ReadOnlySpan naar ReadOnlySpan | static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| string naar ReadOnlySpan | static ReadOnlySpan<char> MemoryExtensions.AsSpan(string) |
Houd er rekening mee dat MemoryExtensions.AsSpan wordt gebruikt in plaats van de equivalente impliciete operator die is gedefinieerd op string.
Dit betekent dat het codegen verschilt tussen LangVersions (de impliciete operator wordt gebruikt in C# 13; de statische methode AsSpan wordt gebruikt in C# 14).
Aan de andere kant kan de conversie worden verzonden op .NET Framework (de AsSpan methode bestaat daar, terwijl de operator string niet).
De expliciete matrix naar (ReadOnly)Span-conversie converteert eerst expliciet van de bronmatrix naar een matrix met het doelelementtype en vervolgens naar (ReadOnly)Span via dezelfde helper als een impliciete conversie zou worden gebruikt, bijvoorbeeld de bijbehorende op_Implicit(T[]).
Betere conversie van uitdrukking
Betere conversie van uitdrukking (§12.6.4.5) wordt bijgewerkt om voorkeur te geven aan impliciete spanconversies. Dit is gebaseerd op wijzigingen in overbelastingsresolutie van verzamelingsexpressies.
Gezien een impliciete conversie
C₁die wordt geconverteerd van een expressieEnaar een typeT₁en een impliciete conversieC₂die wordt geconverteerd van een expressieEnaar een typeT₂, isC₁een betere conversie danC₂als een van de volgende bewaringen geldt:
Eis een verzamelingsexpressieenC₁is een betere verzamelingsconversie van expressies danC₂Eis geen verzamelingsexpressie en een van de volgende is waar:
Ekomt precies overeen metT₁enEkomt niet precies overeen metT₂Ekomt exact overeen met geen vanT₁enT₂, enC₁is een impliciete spanconversie enC₂geen impliciete spanconversieEkomt exact overeen met zowelT₁alsT₂, komt zowel overeen met geen van beidenC₁enC₂, die een implicieteconversie zijn, enT₁is een beter conversiedoelwit danT₂Eis een methodegroep,T₁compatibel is met de beste methode uit de methodegroep voor conversieC₁enT₂niet compatibel is met de beste methode uit de methodegroep voor conversieC₂
Beter conversiedoelstelling
Beter conversiedoel (§12.6.4.7) wordt bijgewerkt om de voorkeur te geven aan ReadOnlySpan<T> boven Span<T>.
Voor de twee typen
T₁enT₂isT₁een beter conversiedoel danT₂als een van de volgende voorwaarden geldt:
T₁isSystem.ReadOnlySpan<E₁>,T₂isSystem.Span<E₂>en bestaat er een identiteitsconversie vanE₁naarE₂T₁isSystem.ReadOnlySpan<E₁>,T₂isSystem.ReadOnlySpan<E₂>en er bestaat een impliciete conversie vanT₁naarT₂en er bestaat geen impliciete conversie vanT₂naarT₁- dat ten minste één van
T₁ofT₂nietSystem.ReadOnlySpan<Eᵢ>is en nietSystem.Span<Eᵢ>is, en er een impliciete conversie vanT₁naarT₂bestaat en geen impliciete conversie vanT₂naarT₁bestaat- ...
Designvergaderingen:
- 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
Opmerkingen over betere prestaties
De betere conversie van expressies regel moet ervoor zorgen dat wanneer een overbelasting van toepassing wordt vanwege de nieuwe spanconversies, elke mogelijke dubbelzinnigheid met een andere overbelasting wordt vermeden omdat de nieuwe overbelasting de voorkeur heeft.
Zonder deze regel zou de volgende code die is gecompileerd in C# 13 leiden tot een dubbelzinnigheidsfout in C# 14 vanwege de nieuwe impliciete standaardconversie van matrix naar ReadOnlySpan die van toepassing is op een ontvanger van de extensiemethode:
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) { }
}
Met de regel kunt u ook nieuwe API's introduceren die eerder leiden tot dubbelzinnigheid, bijvoorbeeld:
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
}
Waarschuwing
Omdat de beterheidsregel is gedefinieerd voor de spanconversies die alleen in LangVersion >= 14bestaan, kunnen API-auteurs dergelijke nieuwe overbelastingen niet toevoegen als ze ondersteunende gebruikers op LangVersion <= 13willen houden.
Als .NET 9 BCL bijvoorbeeld dergelijke overbelastingen introduceert, zullen gebruikers die upgraden naar net9.0 TFM maar op een lagere LangVersion blijven, dubbelzinnigheidsfouten tegenkomen in de bestaande code.
Zie ook een open vraag hieronder.
Type-inferentie
We werken de sectie type-inferenties van de specificatie als volgt bij (wijzigingen in vet).
12.6.3.9 Exacte deducties
Een exacte inferentievan een type
Unaar een typeVwordt als volgt gemaakt:
- Als
Veen van de niet-vastgesteldeXᵢis, wordtUtoegevoegd aan de set van exacte limieten voorXᵢ.- Anders worden sets
V₁...VₑenU₁...Uₑbepaald door te controleren of een van de volgende gevallen van toepassing is:
Vis een matrixtypeV₁[...]enUis een matrixtypeU₁[...]van dezelfde rangVis eenSpan<V₁>enUis een matrixtypeU₁[]of eenSpan<U₁>Vis eenReadOnlySpan<V₁>enUis een matrixtypeU₁[]of eenSpan<U₁>ofReadOnlySpan<U₁>Vis het typeV₁?enUhet typeU₁Vis een samengesteld typeC<V₁...Vₑ>enUeen samengesteld typeC<U₁...Uₑ>
Als een van deze gevallen van toepassing is, wordt er een exacte inferentie gemaakt van elkeUᵢnaar de bijbehorendeVᵢ.- Anders worden er geen deducties gemaakt.
12.6.3.10 Ondergrensdeducties
Een ondergrensdeductie van een type
Unaar een typeVwordt als volgt gemaakt:
- Als
Veen van de niet-vastgesteldeXᵢis, wordtUtoegevoegd aan de set van ondergrenzen voorXᵢ.- Als
Vvan het typeV₁?is enUvan het typeU₁?, wordt er een ondergrensinferentie gemaakt vanU₁naarV₁.- Anders worden sets
U₁...UₑenV₁...Vₑbepaald door te controleren of een van de volgende gevallen van toepassing is:
Vis een matrixtypeV₁[...]enUis een matrixtypeU₁[...]van dezelfde rangVis eenSpan<V₁>enUis een matrixtypeU₁[]of eenSpan<U₁>Vis eenReadOnlySpan<V₁>enUis een matrixtypeU₁[]of eenSpan<U₁>ofReadOnlySpan<U₁>Vis een vanIEnumerable<V₁>,ICollection<V₁>,IReadOnlyList<V₁>>,IReadOnlyCollection<V₁>ofIList<V₁>enUeen enkeldimensionaal matrixtypeU₁[]Vis een samengesteldclass,struct,interfaceofdelegatetypeC<V₁...Vₑ>en er is een uniek typeC<U₁...Uₑ>zodanig datU(of, alsUeen typeparameteris, de effectieve basisklasse of een lid van de effectieve interfaceset) identiek is aan,inheritsvan (direct of indirect) of implementeert (direct of indirect)C<U₁...Uₑ>.- (De beperking 'uniekheid' betekent dat in het geval van interface
C<T>{} class U: C<X>, C<Y>{}er geen afleiding wordt gemaakt bij het afleiden vanUnaarC<T>, omdatU₁zou kunnen zijnXofY.)
Als een van deze gevallen van toepassing is, wordt van elkeUᵢeen deductie gemaakt op de bijbehorendeVᵢals volgt:- Als het niet bekend is dat
Uᵢeen verwijzingstype is, wordt er een exacte inferentie gemaakt- Als
Ueen arraytype is, dan wordteen ondergrensdeductie gemaaktwaarbij de deductie afhankelijk is van het typeV:
- Als
VeenSpan<Vᵢ>is, wordt een exacte deductie gemaakt- Als
Veen matrixtype of eenReadOnlySpan<Vᵢ>is, wordt er een ondergrensinferentie gemaakt- Anders, als
UeenSpan<Uᵢ>is, hangt de afleiding af van het typeV:
- Als
VeenSpan<Vᵢ>is, wordt een exacte deductie gemaakt- Als
VeenReadOnlySpan<Vᵢ>is, wordt er een ondergrensinferentie gemaakt- Anders, als
UeenReadOnlySpan<Uᵢ>is enVeenReadOnlySpan<Vᵢ>is, wordt een ondergrensdeductie gemaakt:- Als
VC<V₁...Vₑ>is, is de inferentie afhankelijk van dei-thtypeparameter vanC:
- Als deze covariant is, wordt er een ondergrensinferentie gemaakt.
- Als het contravariant is, wordt er een bovengrensdeductie gemaakt.
- Als deze invariant is, wordt er een exacte deductie gemaakt.
- Anders worden er geen deducties gemaakt.
Er zijn geen regels voor bovengrensafleiding omdat het niet mogelijk zou zijn om ze te bereiken.
Type-inferentie begint nooit als bovengrens; het moet eerst door een ondergrens-inferentie en een contravariante typeparameter gaan.
Vanwege de regel 'als Uᵢ niet bekend is als een verwijzingstype, kan een exacte deductie worden gemaakt', kan het brontypeargument niet worden Span/ReadOnlySpan (deze kunnen geen verwijzingstypen zijn).
De inferentie van de bovengrens is echter alleen van toepassing als het brontype een Span/ReadOnlySpanis, aangezien het regels zou hebben zoals:
Uis eenSpan<U₁>enVis een matrixtypeV₁[]of eenSpan<V₁>Uis eenReadOnlySpan<U₁>enVis een matrixtypeV₁[]of eenSpan<V₁>ofReadOnlySpan<V₁>
Brekende wijzigingen
Zoals elk voorstel dat de conversies van bestaande scenario's verandert, introduceert dit voorstel enkele nieuwe ingrijpende veranderingen. Hier volgen enkele voorbeelden:
Reverse aanroepen op een matrix
Het aanroepen van x.Reverse() waarin x een instantie is van type T[], zou eerder binden aan IEnumerable<T> Enumerable.Reverse<T>(this IEnumerable<T>), maar wordt nu gekoppeld aan void MemoryExtensions.Reverse<T>(this Span<T>).
Helaas zijn deze API's niet compatibel (de laatste voert de omkering ter plekke uit en retourneert void).
.NET 10 beperkt dit door een matrixspecifieke overbelasting toe te voegen IEnumerable<T> Reverse<T>(this T[]), zie 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
}
Zie ook:
- 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
Ontwerpvergadering: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse
Dubbelzinnigheden
De volgende voorbeelden, waarvoor de typedeductie eerder mislukte bij de overload van Span, slagen nu omdat de typedeductie van matrix naar Span is gelukt; daarom zijn ze nu dubbelzinnig.
Om dit te omzeilen, kunnen gebruikers .AsSpan() of API-auteurs OverloadResolutionPriorityAttributegebruiken.
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 voegt meer overbelastingen toe om dit te verhelpen: https://github.com/xunit/xunit/discussions/3021.
Ontwerpvergadering: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#new-ambiguities
Covariantmatrices
Overbelastingen die IEnumerable<T> voor covariante arrays werkten, maar overbelastingen die Span<T> (die we nu liever gebruiken) niet, omdat de spanconversie een ArrayTypeMismatchException genereert voor covariante arrays.
Je zou kunnen beweren dat de Span<T> overbelasting niet zou moeten bestaan en dat in plaats daarvan ReadOnlySpan<T> zou moeten worden gebruikt.
Om dit te omzeilen, kunnen gebruikers .AsEnumerable()gebruiken of API-auteurs OverloadResolutionPriorityAttribute gebruiken of ReadOnlySpan<T> overbelasting toevoegen die de voorkeur heeft vanwege de betere regel.
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);
}
Ontwerpvergadering: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#covariant-arrays
Voorkeur voor ReadOnlySpan boven Span
De beterheidsregel veroorzaakt de voorkeur van ReadOnlySpan-overbelastingen over Span-overbelastingen om ArrayTypeMismatchExceptions in covariante matrixscenario'ste voorkomen.
Dit kan leiden tot compilatiefouten in sommige scenario's, bijvoorbeeld wanneer de overbelastingen verschillen door hun retourtype.
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;
}
Zie https://github.com/dotnet/roslyn/issues/76443.
Expressiebomen
Overbelastingen die spans gebruiken zoals MemoryExtensions.Contains hebben de voorkeur boven klassieke overbelastingen zoals Enumerable.Contains, zelfs binnen expressiebomen, maar refstructs worden niet ondersteund door de interpreterengine.
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
Op dezelfde manier moeten vertaalmachines zoals LINQ-to-SQL hierop reageren als hun boomtraversers Enumerable.Contains verwachten, omdat ze in plaats daarvan MemoryExtensions.Contains tegenkomen.
Zie ook:
- 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
Designvergaderingen:
- 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
Door de gebruiker gedefinieerde conversies via overname
Door impliciete spanconversies toe te voegen aan de lijst met standaard impliciete conversies, kunnen we mogelijk gedrag wijzigen wanneer door de gebruiker gedefinieerde conversies betrokken zijn bij een typehiërarchie. In dit voorbeeld ziet u dat wijziging, in vergelijking met een scenario met een geheel getal dat zich al gedraagt als het nieuwe C# 14-gedrag dat doet.
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");
}
}
(Zie ook: https://github.com/dotnet/roslyn/issues/78314
Zoekactie voor extensiemethode
Door impliciete spanconversies toe te staan bij het opzoeken van extensiemethoden, kunnen we mogelijk wijzigen welke extensiemethode wordt opgelost door overbelastingsresolutie.
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");
}
}
}
Open vragen
Onbeperkte betereheidsregel
Moeten we de beterheidsregel onvoorwaardelijk voor LangVersion maken? Hierdoor kunnen API-auteurs nieuwe Span-API's toevoegen waarbij IEnumerable-equivalenten bestaan zonder gebruikers te breken in oudere LangVersions of andere compilers of talen (bijvoorbeeld VB). Dit betekent echter dat gebruikers anders gedrag kunnen krijgen na het bijwerken van de toolset (zonder LangVersion of TargetFramework te wijzigen):
- Compiler kan verschillende overbelastingen kiezen (technisch een belangrijke wijziging, maar hopelijk hebben deze overbelastingen gelijkwaardig gedrag).
- Andere onderbrekingen kunnen zich voordoen, onbekend op dit moment.
Houd er rekening mee dat OverloadResolutionPriorityAttribute dit niet volledig kan oplossen omdat het ook wordt genegeerd op oudere LangVersions.
Het moet echter mogelijk zijn om deze te gebruiken om dubbelzinnigheden van VB te voorkomen waar het kenmerk moet worden herkend.
Meer door de gebruiker gedefinieerde conversies negeren
We hebben een set typeparen gedefinieerd waarvoor taalgedefinieerde impliciete en expliciete spanconversies zijn.
Wanneer een taalgedefinieerde spanconversie bestaat van T1 tot T2, wordt elke door de gebruiker gedefinieerde conversie van T1 naar T2genegeerd (ongeacht de door de gebruiker gedefinieerde conversie die impliciet of expliciet is).
Houd er rekening mee dat dit alle voorwaarden omvat, dus er is geen spanconversie van Span<object> naar ReadOnlySpan<string> (er is een overspanningsconversie van Span<T> naar ReadOnlySpan<U>, maar het moet bevatten dat T : U), vandaar dat een door de gebruiker gedefinieerde conversie wordt overwogen tussen deze typen als deze bestaat (dat zou een gespecialiseerde conversie moeten zijn zoals Span<T> naar ReadOnlySpan<string> omdat conversieoperators geen algemene parameters hebben).
Moeten we door de gebruiker gedefinieerde conversies ook negeren tussen andere combinaties van matrix-/Span-/ReadOnlySpan-/tekenreekstypen waar geen overeenkomende taalomzetting bestaat?
Als er bijvoorbeeld een door de gebruiker gedefinieerde conversie is van ReadOnlySpan<T> naar Span<T>, moeten we deze negeren?
Specifieke mogelijkheden om rekening mee te houden:
-
Wanneer er een spanconversie bestaat van
T1totT2, negeert u een door de gebruiker gedefinieerde conversie vanT1naarT2of vanT2naarT1. -
Door de gebruiker gedefinieerde conversies worden niet overwogen bij het converteren tussen
- een enkeldimensionale
array_typeenSystem.Span<T>/System.ReadOnlySpan<T>, - elke combinatie van
System.Span<T>/System.ReadOnlySpan<T>, -
stringenSystem.ReadOnlySpan<char>.
- een enkeldimensionale
- Zoals hierboven, maar het laatste opsommingsteken vervangt door:
-
stringenSystem.Span<char>/System.ReadOnlySpan<char>.
-
- Zoals hierboven, maar het laatste opsommingsteken vervangt door:
-
stringenSystem.Span<T>/System.ReadOnlySpan<T>.
-
Technisch gezien is het niet mogelijk om een aantal van deze door de gebruiker gedefinieerde conversies te definiëren: het is niet mogelijk om een door de gebruiker gedefinieerde operator te definiëren tussen typen waarvoor een niet-door de gebruiker gedefinieerde conversie bestaat (§10.5.2).
Maar Roslyn schendt dit deel van de specificatie opzettelijk. En sommige conversies, zoals tussen Span en string, zijn toch toegestaan (er bestaat geen taalomzetting tussen deze typen).
Als alternatief voor het simpelweg negeren van de conversies, zouden we kunnen overwegen om ze helemaal niet te definiëren en wellicht de specificatieoverschrijding te vermijden, in ieder geval voor deze nieuwe spanconversies. Dat wil zeggen, Roslyn aanpassen om een compilatiefout te melden als deze conversies worden gedefinieerd (waarschijnlijk behalve die al door de BCL gedefinieerd zijn).
Alternatieven
Houd dingen zoals ze zijn.
C# feature specifications