Dela via


Förstklassiga spantyper

Anmärkning

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningarna (Language Design Meeting).

Du kan lära dig mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-fråga: https://github.com/dotnet/csharplang/issues/8714

Sammanfattning

Vi introducerar förstklassigt stöd för Span<T> och ReadOnlySpan<T> på språket, inklusive nya implicita konverteringstyper och överväger dem på fler platser, vilket möjliggör mer naturlig programmering med dessa integrerade typer.

Motivation

Sedan introduktionen i C# 7.2 har Span<T> och ReadOnlySpan<T> arbetat sig in i språk- och basklassbiblioteket (BCL) på många viktiga sätt. Detta är bra för utvecklare eftersom deras introduktion förbättrar prestandan utan att det kostar utvecklarna säkerhet. Språket har dock hållit dessa typer på armlängds avstånd på några viktiga sätt, vilket gör det svårt att uttrycka avsikten med API:er och leder till en betydande mängd duplicering av ytan för nya API:er. Till exempel har BCL lagt till ett antal nya tensor primitiva API:er i .NET 9, men dessa API:er erbjuds alla på ReadOnlySpan<T>. C# känner inte igen relationen mellan ReadOnlySpan<T>, Span<T>och T[], så även om det finns användardefinierade konverteringar mellan dessa typer kan de inte användas för tilläggsmetodmottagare, kan inte skrivas med andra användardefinierade konverteringar och hjälper inte till med alla allmänna typinferensscenarier. Användare skulle behöva använda explicita konverteringar eller typargument, vilket innebär att IDE-verktyg inte vägleder användarna att använda dessa API:er, eftersom inget tyder på för IDE att det är giltigt att skicka dessa typer efter konverteringen. För att ge maximal användbarhet för det här API-formatet måste BCL definiera en hel uppsättning Span<T> och T[] överlagringar, vilket är mycket duplicerad yta att underhålla utan verklig vinst. Det här förslaget syftar till att lösa problemet genom att låta språket känna igen dessa typer och konverteringar mer direkt.

Till exempel kan BCL bara lägga till en överbelastning för någon MemoryExtensions-hjälpare som:

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);
}

Tidigare behövdes span- och matrisöverlagringar för att göra tilläggsmetoden användbar för variabler med span/matristyp eftersom användardefinierade konverteringar (som finns mellan Span/array/ReadOnlySpan) inte beaktas för tilläggsmottagare.

Detaljerad design

Ändringarna i det här förslaget kommer att kopplas till LangVersion >= 14.

Konverteringsspann

Vi lägger till en ny typ av implicit konvertering i listan i §10.2.1, en implicit span-konvertering. Den här konverteringen är en konvertering från typ och definieras på följande sätt:


En implicit span-konvertering tillåter att array_types, System.Span<T>, System.ReadOnlySpan<T>och string konverteras mellan varandra på följande sätt:

  • Från en endimensionell array_type med elementtyp Ei till System.Span<Ei>
  • Från en endimensionell array_type med elementtyp Ei till System.ReadOnlySpan<Ui>, förutsatt att Ei är covarianskonvertibel (§18.2.3.3) till Ui
  • Från System.Span<Ti> till System.ReadOnlySpan<Ui>, förutsatt att Ti är kovariansomvandlingsbar (§18.2.3.3) till Ui
  • Från System.ReadOnlySpan<Ti> till System.ReadOnlySpan<Ui>, förutsatt att Ti är kovariansomvandlingsbar (§18.2.3.3) till Ui
  • Från string till System.ReadOnlySpan<char>

Alla Span/ReadOnlySpan-typer anses vara tillämpliga för konverteringen om de är ref structoch matchar med deras fullständigt kvalificerade namn (LDM 2024-06-24).

Vi lägger också till implicit span konvertering till listan över implicita standardkonverteringar (§10.4.2). Detta möjliggör att överbelastningslösning kan ta hänsyn till dem vid argumentlösning, som i det tidigare länkade API-förslaget.

De explicita span-konverteringarna är följande:

  • Alla implicita konverteringar av span.
  • Från en array_type med elementtyp Ti till System.Span<Ui> eller System.ReadOnlySpan<Ui> förutsatt att det finns en explicit referenskonvertering från Ti till Ui.

Det finns ingen explicit standardkonvertering till skillnad från andra explicita standardkonverteringar () (§10.4.3) som alltid existerar med hänsyn till den motsatta implicita standardkonverteringen.

Användardefinierade konverteringar

Användardefinierade konverteringar beaktas inte när du konverterar mellan typer för vilka det finns en implicit eller explicit span-konvertering.

Implicita spannkonverteringar undantas från regeln om att det inte går att definiera en användardefinierad operator mellan typer för vilka det finns en icke-användardefinierad konvertering (§10.5.2 Tillåtna användardefinierade konverteringar). Detta behövs så att BCL kan fortsätta att definiera befintliga Span-konverteringsoperatorer även när de växlar till C# 14 (de behövs fortfarande för lägre LangVersions och även eftersom dessa operatorer används i codegen för de nya standardomvandlingarna). Men det kan ses som en implementeringsinformation (codegen och lägre LangVersions är inte en del av specifikationen) och Roslyn bryter mot den här delen av specifikationen ändå (den här specifika regeln om användardefinierade konverteringar tillämpas inte).

Tilläggsmottagare

Vi lägger också till implicit span-konvertering i listan över acceptabla implicita konverteringar på den första parametern för en tilläggsmetod när man fastställer tillämpligheten (12.8.9.3) (ändra i fetstil):

En tilläggsmetod Cᵢ.Mₑ är berättigad om:

  • Cᵢ är en icke-generisk, icke-nästlad klass
  • Namnet på Mₑ är identifierare
  • Mₑ är tillgängligt och tillämpligt när det tillämpas på argumenten som en statisk metod enligt ovan
  • Det finns en implicit identitetsreferens eller boxning, boxning eller span-konvertering från uttryck till typen för den första parametern i Mₑ. Span-konvertering beaktas inte när överbelastningsupplösning utförs för en metodgruppskonvertering.

Observera att implicit span-konvertering inte beaktas för tilläggsmottagare i metodgruppkonverteringar (LDM 2024-07-15) vilket gör att följande kod fortsätter att fungera i stället för att resultera i ett kompileringsfel 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);
}

Som ett möjligt framtida arbete kan vi överväga att ta bort villkoret att spännkonvertering inte beaktas för utökningsmottagare vid metodgruppkonverteringar och i stället ändra så att ett scenario som det ovan skulle framgångsrikt anropa Span-överbelastningen i stället:

  • Kompilatorn kan generera en "thunk" som tar matrisen som mottagare och utför span-konverteringen internt (likt hur användaren manuellt skapar en delegering som x => new int[0].M(x)).
  • Om värdedelegater implementeras kan de direkt ta emot Span som mottagare.

Varians

Målet med avvikelseavsnittet i implicit span-konvertering är att replikera en viss mängd kovarians för System.ReadOnlySpan<T>. Körningsändringar krävs för att fullständigt implementera variansen via generiska objekt här (se .. /csharp-13.0/ref-struct-interfaces.md för användning av ref struct typer i generiska objekt), men vi kan tillåta en begränsad mängd kovarians med hjälp av ett föreslaget .NET 9 API: https://github.com/dotnet/runtime/issues/96952. På så sätt kan språket behandla System.ReadOnlySpan<T> som om T deklarerades som out T i vissa scenarier. Vi utforskar dock inte denna variantkonvertering i alla variansscenarier och inkluderar den inte i definitionen av varianskonverterbar i §18.2.3.3. Om vi i framtiden ändrar körmiljön för att djupare förstå variationen här kan vi göra den mindre brytande ändringen för att den fullt ut ska förstås av språket.

Mönster

Observera att när ref structs används som en typ i ett mönster tillåts endast identitetskonverteringar:

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 { }

Från specifikationen av is-typ-operatorn (§12.12.12.1):

Resultatet av åtgärden E is T [...] är ett Boolean-värde som anger om E är icke-null och kan konverteras till typ T av en referenskonvertering, en boxningskonvertering, en omslutningskonvertering, en avslutningskonvertering eller en omskrivningskonvertering.

[...]

Om T är en värdetyp som inte kan nulleras true resultatet om D och T är av samma typ.

Det här beteendet ändras inte med den här funktionen, därför går det inte att skriva mönster för Span/ReadOnlySpan, även om liknande mönster är möjliga för matriser (inklusive varians):

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
}

Kodgenerering

Konverteringarna finns alltid, oavsett om det finns någon runtime-hjälp som används för att implementera dem (LDM 2024-05-13). Om hjälparna inte finns resulterar försök att använda konverteringen i ett kompileringsfel om att en kompilator-obligatorisk medlem saknas.

Kompilatorn förväntar sig att använda följande hjälp eller motsvarigheter för att implementera konverteringarna:

Omvandling Hjälpare
array till span static implicit operator Span<T>(T[]) (definierat i Span<T>)
matris till ReadOnlySpan static implicit operator ReadOnlySpan<T>(T[]) (definierat i ReadOnlySpan<T>)
Span till ReadOnlySpan static implicit operator ReadOnlySpan<T>(Span<T>) (definieras i Span<T>) och static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>)
ReadOnlySpan till ReadOnlySpan static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>)
konvertera sträng till ReadOnlySpan static ReadOnlySpan<char> MemoryExtensions.AsSpan(string)

Observera att MemoryExtensions.AsSpan används i stället för motsvarande implicita operator som definierats på string. Det innebär att kodgenen skiljer sig mellan LangVersions (den implicita operatorn används i C# 13; den statiska metoden AsSpan används i C# 14). Å andra sidan kan konverteringen genereras på .NET Framework (metoden AsSpan finns där medan string operatorn inte gör det).

Den explicita matrisen till (ReadOnly)Span-konvertering konverterar först explicit från källmatrisen till en matris med målelementtypen och sedan till (ReadOnly)Span via samma hjälp som en implicit konvertering skulle använda, d.v.s. motsvarande op_Implicit(T[]).

Bättre omvandling av uttryck

Bättre konvertering från uttryck (§12.6.4.5) har uppdaterats för att ge företräde åt implicita spannkonverteringar. Detta baseras på ändringar av överbelastningsmatchning för samlingsuttryck.

Med en implicit konvertering C₁ som konverterar från ett uttryck E till en typ T₁och en implicit konvertering C₂ som konverterar från ett uttryck E till en typ T₂är C₁ en bättre konvertering än C₂ om något av följande gäller:

  • E är ett samlingsuttryckoch C₁ är en bättre samlingskonvertering från uttryck än C₂
  • E är inte ett samlingsuttryck och något av följande gäller:
    • E stämmer exakt med T₁ och E stämmer inte exakt med T₂
    • E matchar exakt ingen av T₁ och T₂, och C₁ är en implicit span-konvertering och C₂ är inte en implicit spankonvertering
    • E exakt matchar antingen båda eller inget av T₁ och T₂, antingen båda eller inget av C₁ och C₂ är en implicit intervallkonvertering, och T₁ är ett bättre konverteringsmål än T₂
  • E är en metodgrupp, T₁ är kompatibel med den bästa metoden från metodgruppen för konvertering C₁, och T₂ är inte kompatibel med den bästa metoden från metodgruppen för konvertering C₂.

Bättre konverteringsmål

Bättre konverteringsmål (§12.6.4.7) uppdateras för att föredra ReadOnlySpan<T> framför Span<T>.

Med två typer T₁ och T₂är T₁ ett bättre konverteringsmål än T₂ om något av följande gäller:

  • T₁ är System.ReadOnlySpan<E₁>, T₂ är System.Span<E₂>och en identitetskonvertering från E₁ till E₂ finns
  • T₁ är System.ReadOnlySpan<E₁>, T₂ är System.ReadOnlySpan<E₂>, och det finns en implicit konvertering från T₁ till T₂ och ingen implicit konvertering från T₂ till T₁ finns
  • Minst en av T₁ eller T₂ är inte System.ReadOnlySpan<Eᵢ> och är inte System.Span<Eᵢ>och en implicit konvertering från T₁ till T₂ finns och ingen implicit konvertering från T₂ till T₁ finns
  • ...

Designmöten:

Betterness-kommentarer

Den bättre konverteringen från uttrycket-regel bör se till att när en överbelastning blir tillämplig på grund av de nya span-konverteringarna, undviks eventuell tvetydighet med en annan överbelastning eftersom den nyligen tillämpliga överbelastningen föredras.

Utan den här regeln skulle följande kod som har kompilerats i C# 13 resultera i ett tvetydighetsfel i C# 14 på grund av den nya implicita standardkonverteringen från matris till ReadOnlySpan som gäller för en tilläggsmetodmottagare:

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) { }
}

Regeln tillåter också införandet av nya API:er som tidigare skulle resultera i tvetydigheter, till exempel:

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
}

Varning

Eftersom regeln för förbättring definieras för de spannkonverteringar som endast finns i LangVersion >= 14kan API-författare inte lägga till sådana nya överladdningar om de vill fortsätta att stödja användare på LangVersion <= 13. Om till exempel .NET 9 BCL introducerar sådana överbelastningar får användare som uppgraderar till net9.0 TFM men fortsätter använda en lägre LangVersion tvetydighetsfel i befintlig kod. Se även en öppen fråga nedan.

Typ av inferens

Vi uppdaterar avsnittet typinferenser i specifikationen enligt följande (ändringar i fetstil).

12.6.3.9 Exakta slutsatsdragningar

En exakt slutsatsdragningfrån en typ Utill en typ V görs enligt följande:

  • Om V är en av de ofixeradeXᵢ, läggs U till i uppsättningen av exakta gränser för Xᵢ.
  • Annars bestäms uppsättningar V₁...Vₑ och U₁...Uₑ genom att kontrollera om något av följande fall gäller:
    • V är en matristyp V₁[...] och U är en matristyp U₁[...] av samma rangordning
    • V är en Span<V₁> och U är en matristyp U₁[] eller en Span<U₁>
    • V är en ReadOnlySpan<V₁> och U är en matristyp U₁[] eller en Span<U₁> eller ReadOnlySpan<U₁>
    • V är typen V₁? och U är typen U₁
    • V är en konstruerad typ C<V₁...Vₑ> och U är en konstruerad typ C<U₁...Uₑ>
      Om något av dessa fall gäller görs en exakt slutsatsdragning från varje Uᵢ till det motsvarande Vᵢ.
  • Annars görs inga slutsatsdragningar.

12.6.3.10 Lägre bundna slutsatsdragningar

En lägre bunden slutsats från en typ Utill en typ V görs på följande sätt:

  • Om V är en av de ofixeradeXᵢ läggs U till i uppsättningen med lägre gränser för Xᵢ.
  • Annars, om V är typen V₁? och U är typen U₁?, görs en nedre gränsinferens från U₁ till V₁.
  • Annars bestäms uppsättningar U₁...Uₑ och V₁...Vₑ genom att kontrollera om något av följande fall gäller:
    • V är en matristyp V₁[...] och U är en matristyp U₁[...]av samma rangordning
    • V är en Span<V₁> och U är en matristyp U₁[] eller en Span<U₁>
    • V är en ReadOnlySpan<V₁> och U är en matristyp U₁[] eller en Span<U₁> eller ReadOnlySpan<U₁>
    • V är en av IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>, IReadOnlyCollection<V₁> eller IList<V₁> och U är en endimensionell matristyp U₁[]
    • V är en konstruerad class, struct, interface eller delegate typ C<V₁...Vₑ> och det finns en unik typ C<U₁...Uₑ> så att U (eller, om U är en typ parameter, dess effektiva basklass eller någon medlem i dess effektiva gränssnittsuppsättning) är identisk med, inherits från (direkt eller indirekt) eller implementerar (direkt eller indirekt) C<U₁...Uₑ>.
    • (Begränsningen "unikhet" innebär att i det fall gränssnittet C<T>{} class U: C<X>, C<Y>{}görs ingen slutsatsdragning vid slutsatsdragning från U till C<T> eftersom U₁ kan vara X eller Y.)
      Om något av dessa fall gäller görs en slutsats från varje Uᵢ till motsvarande Vᵢ enligt följande:
    • Om Uᵢ inte är känd för att vara en referenstyp görs en exakt slutsatsdragning
    • Om U är av matristyp görs däremot en nedre bindningsinförselV:
      • Om V är en Span<Vᵢ>görs en exakt inferens
      • Om V är en matristyp eller en ReadOnlySpan<Vᵢ>görs en lägre slutsatsdragning
    • Om U annars är en Span<Uᵢ> beror slutsatsdragningen på typen av V:
      • Om V är en Span<Vᵢ>görs en exakt inferens
      • Om V är en ReadOnlySpan<Vᵢ>, då görs en nedre gränsinferens
    • Om U annars är en ReadOnlySpan<Uᵢ> och V är en ReadOnlySpan<Vᵢ> görs en lägre slutsatsdragning:
    • Om V är C<V₁...Vₑ> beror annars slutsatsdragningen på parametern i-th typ av C:
      • Om den är kovariant görs en undergränsinferens.
      • Om den är kontravariant skapas en övre gränsinferens.
      • Om den är invariant görs en exakt slutsatsdragning.
  • Annars görs inga slutsatsdragningar.

Det finns inga regler för övre gräns för slutsatser eftersom det inte skulle vara möjligt att uppnå dem. Typinferensen börjar aldrig som övre gräns; den måste gå igenom en nedre gränshärledning och en kontravariant typparameter. På grund av regeln "om Uᵢ inte är känd som en referenstyp görs en exakt slutledning ", kunde källtypsargumentet inte vara Span/ReadOnlySpan (de kan inte vara referenstyper). Den övre gränsen skulle dock bara gälla om källtypen var en Span/ReadOnlySpan, eftersom den skulle ha regler som:

  • U är en Span<U₁> och V är en matristyp V₁[] eller en Span<V₁>
  • U är en ReadOnlySpan<U₁> och V är en matristyp V₁[] eller en Span<V₁> eller ReadOnlySpan<V₁>

Brytande förändringar

Som alla förslag som ändrar konverteringar av befintliga scenarier innebär det här några nya brytande ändringar. Här är några exempel:

Anropa Reverse på en matris

Att anropa x.Reverse() där x är en instans av typen T[] skulle tidigare binda till IEnumerable<T> Enumerable.Reverse<T>(this IEnumerable<T>), medan den nu binder till void MemoryExtensions.Reverse<T>(this Span<T>). Tyvärr är dessa API:er inkompatibla (det senare gör återföringen på plats och returnerar void).

.NET 10 minimerar detta genom att lägga till en matrisspecifik överlagring IEnumerable<T> Reverse<T>(this T[]), se 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
}

Se även:

Designmöte: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse

Tvetydigheter

Följande exempel misslyckades tidigare med typinferens för Span-överbelastningen, men typinferens från matris till Span lyckas nu, vilket gör dessa tvetydiga. För att kringgå detta kan användare använda .AsSpan() eller API-författare kan använda 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 lägger till fler överlagringar för att minimera detta: https://github.com/xunit/xunit/discussions/3021.

Designmöte: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#new-ambiguities

Covarianta matriser

Överlagringar som tar IEnumerable<T> fungerade på covariantmatriser, men överlagringar som tar Span<T> (vilket vi nu föredrar) gör det inte, eftersom span-konverteringen genererar en ArrayTypeMismatchException för covariantmatriser. Argumenteras att Span<T> överbelastning borde inte existera, den borde ta ReadOnlySpan<T> istället. För att kringgå detta kan användare använda .AsEnumerable(), och API-författare kan använda OverloadResolutionPriorityAttribute eller lägga till ReadOnlySpan<T> överlagor vilket föredras på grund av regeln för förbättring.

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);
}

Designmöte: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#covariant-arrays

Föredrar ReadOnlySpan framför Span

Regeln för "betterness" gör att ReadOnlySpan-överlagringar föredras framför Span-överlagringar för att undvika i kovarianta matrisscenarier. Det kan leda till kompileringsbrytningar i vissa scenarier, till exempel när överlagringarna skiljer sig åt efter returtyp:

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;
}

Se även https://github.com/dotnet/roslyn/issues/76443.

Uttrycksträd

Överlagringar som tar spännvidder som MemoryExtensions.Contains föredras framför klassiska överlagringar som Enumerable.Contains, även inuti uttrycksträd , men ref structs stöds inte av tolkmotorn:

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

På samma sätt måste översättningsmotorer som LINQ-to-SQL reagera på detta om deras trädbesökare förväntar sig Enumerable.Contains eftersom de kommer att stöta på MemoryExtensions.Contains i stället.

Se även:

Designmöten:

Användardefinierade konverteringar via arv

Genom att lägga till implicita intervallkonverteringar i listan över implicita standardkonverteringar kan vi ändra beteendet när användardefinierade konverteringar ingår i en typhierarki. Det här exemplet visar en förändring i förhållande till ett heltalsscenario som redan uppträder på samma sätt som det nya C# 14-beteendet kommer att göra.

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");
    }
}

Se även: https://github.com/dotnet/roslyn/issues/78314

Tilläggsmetodsökning

Genom att tillåta implicita span-konverteringar i tilläggsmetodsökning kan vi eventuellt ändra vilken tilläggsmetod som löses genom överbelastningslösning.

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");
        }
    }
}

Öppna frågor

Obegränsad bättrehetsregel

Ska vi göra betterness-regeln ovillkorlig för LangVersion? Det skulle göra det möjligt för API-författare att lägga till nya Span-API:er där IEnumerable-motsvarigheter finns utan att bryta användare på äldre LangVersions eller andra kompilatorer eller språk (t.ex. VB). Det skulle dock innebära att användarna kan få olika beteende när de har uppdaterat verktygsuppsättningen (utan att ändra LangVersion eller TargetFramework):

  • Kompilatorn kan välja olika överladdningar (tekniskt sett en förändring som bryter kompatibilitet, men förhoppningsvis skulle dessa överladdningar ha motsvarande beteende).
  • Andra pauser kan uppstå, som för närvarande är okända.

Observera att OverloadResolutionPriorityAttribute inte kan lösa detta helt eftersom det också ignoreras på äldre LangVersions. Det bör dock vara möjligt att använda det för att undvika tvetydigheter från VB där attributet ska identifieras.

Ignorera fler användardefinierade konverteringar

Vi definierade en uppsättning typpar för vilka det finns språkdefinierade implicita och explicita span-konverteringar. När det finns en språkdefinierad spannkonvertering från T1 till T2ignoreras alla användardefinierade konverteringar från T1 till T2 (oavsett om intervallet och den användardefinierade konverteringen är implicit eller explicit).

Observera att detta omfattar alla villkor, så det finns till exempel ingen span-konvertering från Span<object> till ReadOnlySpan<string> (det finns en span-konvertering från Span<T> till ReadOnlySpan<U> men den måste innehålla den T : U), och därför skulle en användardefinierad konvertering övervägas mellan dessa typer om den fanns (det måste vara en specialiserad konvertering som Span<T> till ReadOnlySpan<string> eftersom konverteringsoperatorer inte kan ha generiska parametrar).

Ska vi ignorera användardefinierade konverteringar även mellan andra kombinationer av matris/Span/ReadOnlySpan/strängtyper där det inte finns någon motsvarande språkdefinierad span-konvertering? Om det till exempel finns en användardefinierad konvertering från ReadOnlySpan<T> till Span<T>, bör vi ignorera den?

Specificera alternativ att överväga:

  1. När det finns en span-konvertering från T1 till T2ignorerar du alla användardefinierade konverteringar från T1 till T2eller från T2 till T1.

  2. Användardefinierade konverteringar beaktas inte vid konvertering mellan

    • någon endimensionell array_type och System.Span<T>/System.ReadOnlySpan<T>,
    • en kombination av System.Span<T>/System.ReadOnlySpan<T>,
    • string och System.ReadOnlySpan<char>.
  3. Precis som ovan men ersätter den sista punktpunkten med:
    • string och System.Span<char>/System.ReadOnlySpan<char>.
  4. Precis som ovan men ersätter den sista punktpunkten med:
    • string och System.Span<T>/System.ReadOnlySpan<T>.

Tekniskt sett tillåter specifikationen inte att vissa av dessa användardefinierade konverteringar ens definieras: det går inte att definiera en användardefinierad operator mellan typer för vilka en icke-användardefinierad konvertering finns (§10.5.2). Men Roslyn bryter avsiktligt mot den här delen av specifikationen. Och vissa konverteringar som mellan Span och string tillåts ändå (det finns ingen språkdefinierad konvertering mellan dessa typer).

Vi kan dock, istället för att bara ignorera konverteringarna, inte tillåta att de definieras alls och kanske bryta specifikationsöverträdelsen åtminstone när det gäller dessa nya span-konverteringar, dvs. ändra Roslyn för att faktiskt rapportera ett kompileringsfel om dessa konverteringar definieras (med undantag för de som redan definierats av BCL).

Alternativ

Behåll saker som de är.