Freigeben über


Utf8-Zeichenfolgenliterale

Anmerkung

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 Anmerkungen zum Language Design Meeting (LDM) erfasst.

Weitere Informationen zum Prozess für die Aufnahme von Funktions-Speclets in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

Zusammenfassung

Dieser Vorschlag fügt die Möglichkeit hinzu, UTF8-Zeichenfolgenliterale in C# zu schreiben und automatisch in ihre UTF-8-byte-Darstellung codieren zu lassen.

Motivation

UTF8 ist die Sprache des Webs und seine Verwendung ist in bedeutenden Teilen des .NET-Stapels erforderlich. Zwar kommt ein Großteil der Daten in Form von byte[] aus dem Netzwerkstapel, dennoch werden noch in erheblichem Umfang Konstanten im Code verwendet. So muss der Netzwerkstapel beispielsweise häufig Konstanten schreiben wie "HTTP/1.0\r\n", " AUTH" oder "Content-Length: ".

Heute gibt es keine effiziente Syntax dafür, da C# alle Zeichenfolgen mit UTF16-Codierung darstellt. Entwickler müssen sich daher entweder für die bequeme Codierung zur Laufzeit, die mit einem Mehraufwand einhergeht – einschließlich der Zeit, die zu Beginn für die eigentliche Codierung (und Zuordnungen, wenn der Zieltyp diese eigentlich gar nicht benötigt) aufgewendet werden muss –, oder für die manuelle Übersetzung der Bytes und ihre Speicherung in einem byte[] entscheiden.

// Efficient but verbose and error prone
static ReadOnlySpan<byte> AuthWithTrailingSpace => new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
WriteBytes(AuthWithTrailingSpace);

// Incurs allocation and startup costs performing an encoding that could have been done at compile-time
static readonly byte[] s_authWithTrailingSpace = Encoding.UTF8.GetBytes("AUTH ");
WriteBytes(s_authWithTrailingSpace);

// Simplest / most convenient but terribly inefficient
WriteBytes(Encoding.UTF8.GetBytes("AUTH "));

Diese Abwägung ist ein Problem, mit dem unsere Partner in der Laufzeit, ASP.NET und Azure häufig konfrontiert sind. Oftmals geht dies zu Lasten der Leistung, da sie die Mühe scheuen, die byte[]-Codierung von Hand zu schreiben.

Zur Behebung dieses Problems werden wir UTF8-Literale in der Sprache zulassen und diese zur Kompilierungszeit in das UTF8 byte[] codieren.

Detailliertes Design

Suffix u8 in Zeichenfolgenliteralen

Die Sprache stellt das Suffix u8 für Zeichenfolgenliterale bereit, um den Typ UTF8 zu erzwingen. Bei dem Suffix wird nicht zwischen Groß- und Kleinschreibung unterschieden. Das Suffix U8 wird unterstützt und hat dieselbe Bedeutung wie das Suffix u8.

Bei Verwendung des Suffixes u8 ist der Wert des Literals ein ReadOnlySpan<byte>, das eine UTF-8-Byte-Darstellung der Zeichenfolge enthält. Hinter dem letzten Byte im Speicher (und außerhalb der Länge von ReadOnlySpan<byte>) wird ein Nullterminator platziert, um einige Interop-Szenarien zu handhaben, in denen der Aufruf nullterminierte Zeichenfolgen erwartet.

string s1 = "hello"u8;             // Error
var s2 = "hello"u8;                // Okay and type is ReadOnlySpan<byte>
ReadOnlySpan<byte> s3 = "hello"u8; // Okay.
byte[] s4 = "hello"u8;             // Error - Cannot implicitly convert type 'System.ReadOnlySpan<byte>' to 'byte[]'.
byte[] s5 = "hello"u8.ToArray();   // Okay.
Span<byte> s6 = "hello"u8;         // Error - Cannot implicitly convert type 'System.ReadOnlySpan<byte>' to 'System.Span<byte>'.

Da die Literale als globale Konstanten zugewiesen würden, würde die Lebensdauer des daraus resultierenden ReadOnlySpan<byte> nicht verhindern, dass es zurückgegeben oder an andere Stellen weitergegeben wird. In bestimmten Kontexten, vor allem in asynchronen Funktionen, sind jedoch keine lokalen Variablen von Ref-Strukturtypen zulässig. In diesen Situationen wäre die Verwendung daher beeinträchtigt und es wäre ein ToArray()-Aufruf oder Ähnliches erforderlich.

Ein u8-Literal hat keinen konstanten Wert. Das liegt daran, dass ReadOnlySpan<byte> heute nicht der Typ einer Konstante sein kann. Wenn die Definition von const in Zukunft erweitert wird, um ReadOnlySpan<byte>zu berücksichtigen, sollte dieser Wert auch als Konstante betrachtet werden. In der Praxis bedeutet dies jedoch, dass ein u8-Literal nicht als Standardwert eines optionalen Parameters verwendet werden kann.

// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing"u8) { ... } 

Wenn der Eingabetext für das Literal eine falsch formatierte UTF16-Zeichenfolge ist, gibt die Sprache einen Fehler aus:

var bytes = "hello \uD8\uD8"u8; // Error: malformed UTF16 input string

var bytes2 = "hello \uD801\uD802"u8; // Allowed: invalid UTF16 values, but it's correctly formed.

Additionsoperator

Zu §12.10.5 Addition operator wird wie folgt ein neuer Aufzählungspunkt hinzugefügt.

  • Verkettung von UTF8-Bytedarstellungen:

    ReadOnlySpan<byte> operator +(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y);
    

    Dieser binäre +-Operator führt eine Verkettung von Bytesequenzen aus und ist nur anwendbar, wenn beide Operanden semantische UTF8-Bytedarstellungen sind. Ein Operand ist semantisch eine UTF8-Bytedarstellung, wenn es sich entweder um einen Wert eines u8-Literals oder um einen Wert handelt, der durch den Operator für die Verkettung von UTF8-Bytedarstellungen erzeugt wird.

    Das Ergebnis der Verkettung der UTF8-Bytedarstellungen ist ein ReadOnlySpan<byte>, das aus den Bytes des linken Operanden, gefolgt von den Bytes des rechten Operanden besteht. Hinter dem letzten Byte im Speicher (und außerhalb der Länge von ReadOnlySpan<byte>) wird ein Nullterminator platziert, um einige Interop-Szenarien zu handhaben, in denen der Aufruf nullterminierte Zeichenfolgen erwartet.

Reduzierung

Die Sprache reduziert die UTF8-codierten Zeichenfolgen genau so, wie wenn der Entwickler das resultierende byte[]-Literal im Code eingegeben hätte. Zum Beispiel:

ReadOnlySpan<byte> span = "hello"u8;

// Equivalent to

ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 }).
                               Slice(0,5); // The `Slice` call will be optimized away by the compiler.

Das bedeutet, dass alle Optimierungen, die für das new byte[] { ... } Formular gelten, auch für utf8-Literale gelten. Dies bedeutet, dass die Aufrufsite ohne Zuordnung ist, da C# diese so optimiert, dass sie im Abschnitt .data der PE-Datei gespeichert wird.

Mehrere aufeinanderfolgende Anwendungen von Operatoren für die Verkettung von UTF8-Bytedarstellungen werden zu einer einzelnen Erstellung von ReadOnlySpan<byte> mit einem Bytearray zusammengefasst, das die endgültige Bytesequenz enthält.

ReadOnlySpan<byte> span = "h"u8 + "el"u8 + "lo"u8;

// Equivalent to

ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 }).
                               Slice(0,5); // The `Slice` call will be optimized away by the compiler.

Nachteile

Verwendung von Core-APIs

Die Compiler-Implementierung nutzt UTF8Encoding sowohl zur Erkennung ungültiger Zeichenfolgen als auch zur Übersetzung in byte[]. Die genauen APIs hängen möglicherweise davon ab, welches Zielframework der Compiler verwendet. Doch UTF8Encoding wird die Hauptarbeit der Implementierung übernehmen.

In der Vergangenheit hat der Compiler die Verwendung von Laufzeit-APIs für die Verarbeitung von Literalen vermieden. Das liegt daran, dass die Kontrolle über die Verarbeitung der Konstanten dadurch von der Sprache auf die Laufzeit übergeht. Konkret bedeutet dies, dass Elemente wie Fehlerbehebungen die konstante Codierung ändern können und bedeuten, dass das Ergebnis der C#-Kompilierung davon abhängt, auf welcher Laufzeit der Compiler ausgeführt wird.

Dies ist kein hypothetisches Problem. In frühen Versionen von Roslyn wurde double.Parse für die Analyse von Gleitkommakonstanten verwendet. Dies führte zu einer Reihe von Problemen. Zunächst bedeutete dies, dass einige Gleitkommawerte unterschiedliche Darstellungen zwischen dem nativen Compiler und Roslyn hatten. Zweitens, da .NET Core sich weiterentwickelt hat und langjährige Fehler im double.Parse Code behoben hat, bedeutete dies, dass die Bedeutung dieser Konstanten in der Sprache geändert wurde, je nachdem, auf welcher Laufzeit der Compiler ausgeführt wurde. Dies führte dazu, dass der Compiler letztendlich seine eigene Version des Gleitkomma-Analysecodes schrieb und damit die Abhängigkeit von double.Parse beseitigte.

Dieses Szenario wurde mit dem Laufzeitteam diskutiert, und wir haben nicht das Gefühl, dass es dieselben Probleme gibt wie zuvor. Die UTF8-Analyse ist über Laufzeiten hinweg stabil, und es gibt keine bekannten Probleme in diesem Bereich, die zukünftige Kompatibilitätsprobleme verursachen könnten. Wenn eine Möglichkeit auftaucht, können wir die Strategie neu überprüfen.

Alternativen

Nur Zieltyp

Das Design könnte sich nur auf Target Typing verlassen und das Suffix u8 in string-Literalen entfernen. In den meisten Fällen wird das string-Literal heute direkt einem ReadOnlySpan<byte> zugewiesen, daher ist es überflüssig.

ReadOnlySpan<byte> span = "Hello World;" 

Das Suffix u8 besteht hauptsächlich zur Unterstützung von zwei Szenarien: var und Überladungsauflösung. Beachten Sie für letzteres den folgenden Anwendungsfall:

void Write(ReadOnlySpan<byte> span) { ... } 
void Write(string s) {
    var bytes = Encoding.UTF8.GetBytes(s);
    Write(bytes.AsSpan());
}

Angesichts der Implementierung ist es besser, Write(ReadOnlySpan<byte>) aufzurufen, und das Suffix u8 macht dies bequem möglich: Write("hello"u8). Andernfalls müssen Entwickler auf umständliche Umwandlungen von Write((ReadOnlySpan<byte>)"hello") zurückgreifen.

Dennoch handelt es sich hier lediglich um ein praktisches Element, die Funktion kann auch ohne dieses Element bestehen, und es ist kein Problem, es zu einem späteren Zeitpunkt hinzuzufügen.

Warten auf den Utf8String-Typ

Zwar verwendet das .NET-Ökosystem heute standardmäßig ReadOnlySpan<byte> als De-Facto-Utf8-Zeichenfolgentyp, es ist jedoch möglich, dass in der Laufzeit in Zukunft ein Typ Utf8String eingeführt wird.

Wir sollten unser Design hier angesichts dieser möglichen Änderung bewerten und darüber nachdenken, ob wir die entscheidungen bedauern, die wir getroffen haben. Dies sollte jedoch gegen die realistische Wahrscheinlichkeit abgewogen werden, dass wir Utf8Stringeinführen werden, eine Wahrscheinlichkeit, die jeden Tag abnimmt, an dem wir ReadOnlySpan<byte> als akzeptable Alternative betrachten.

Es scheint unwahrscheinlich, dass wir die Zieltypkonvertierung zwischen Zeichenfolgenliteralen und ReadOnlySpan<byte> bereuen würden. Die Verwendung von ReadOnlySpan<byte> als utf8 ist jetzt in unsere APIs eingebettet und daher gibt es immer noch einen Nutzen in der Umwandlung, auch wenn Utf8String eingeführt wird und ein "besserer" Typ ist. Die Sprache könnte einfach Konvertierungen zu Utf8String gegenüber ReadOnlySpan<byte>vorziehen.

Eher würden wir es bereuen, dass das Suffix u8 auf ReadOnlySpan<byte> und nicht auf Utf8String verweist. Das wäre ähnlich, wie wir es bereuen, dass stackalloc int[] den natürlichen Typ int* und nicht Span<int> hat. Dies ist jedoch kein Deal breaker, nur eine Unannehmlichkeit.

Konvertierungen zwischen string-Konstanten und byte-Sequenzen

Die Konvertierungen in diesem Abschnitt wurden nicht implementiert. Diese Umwandlungen bleiben aktive Vorschläge.

Die Sprache ermöglicht Konvertierungen zwischen string-Konstanten und byte-Sequenzen, bei denen der Text in die entsprechende UTF8-Bytedarstellung konvertiert wird. Insbesondere ermöglicht der Compiler eine string_constant_to_UTF8_byte_representation_conversion – implizite Konvertierungen von string-Konstanten in byte[], Span<byte> und ReadOnlySpan<byte>. Im Abschnitt §10.2 zu impliziten Konvertierungen wird ein neuer Aufzählungspunkt hinzugefügt. Diese Konvertierung ist keine Standardkonvertierung §10.4.

byte[] array = "hello";             // new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f }
Span<byte> span = "dog";            // new byte[] { 0x64, 0x6f, 0x67 }
ReadOnlySpan<byte> span = "cat";    // new byte[] { 0x63, 0x61, 0x74 }

Wenn der Eingabetext für die Konvertierung eine falsch formatierte UTF16-Zeichenfolge ist, gibt die Sprache einen Fehler aus:

const string text = "hello \uD801\uD802";
byte[] bytes = text; // Error: the input string is not valid UTF16

Diese Funktion wird voraussichtlich überwiegend mit Literalen verwendet werden, funktioniert aber auch mit jedem string-Konstantenwert. Eine Konvertierung von einer string-Konstante mit null-Wert wird ebenfalls unterstützt. Das Ergebnis der Konvertierung ist der default-Wert des Zieltyps.

const string data = "dog"
ReadOnlySpan<byte> span = data;     // new byte[] { 0x64, 0x6f, 0x67 }

Bei konstanten Operationen an Zeichenfolgen, wie z. B. +, erfolgt die Codierung in UTF-8 beim endgültigen string und wird nicht für die einzelnen Teile durchgeführt, deren Ergebnisse dann verkettet werden. Diese Sortierung ist wichtig zu berücksichtigen, da sie sich darauf auswirken kann, ob die Konvertierung erfolgreich ist oder nicht.

const string first = "\uD83D";  // high surrogate
const string second = "\uDE00"; // low surrogate
ReadOnlySpan<byte> span = first + second;

Die beiden Teile hier sind allein ungültig, da sie unvollständige Bestandteile eines Ersatzzeichenpaars sind. Für sie einzeln gibt es keine richtige Übersetzung in UTF8, doch zusammen bilden sie ein vollständiges Ersatzzeichenpaar, das erfolgreich in UTF8 übersetzt werden kann.

Die string_constant_to_UTF8_byte_representation_conversion ist in Linq-Ausdrucksbaumstrukturen nicht zulässig.

Zwar sind die Eingaben für diese Konvertierungen Konstanten und die Daten werden zum Zeitpunkt der Kompilierung vollständig codiert, die Konvertierung wird von der Sprache jedoch nicht als konstant betrachtet. Das liegt daran, dass Felder heute nicht konstant sind. Wenn die Definition von const in Zukunft erweitert wird, um Arrays in Betracht zu ziehen, sollten diese Konvertierungen ebenfalls berücksichtigt werden. Dies bedeutet jedoch, dass ein Ergebnis dieser Konvertierungen nicht als Standardwert eines optionalen Parameters verwendet werden kann.

// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing") { ... } 

Nach der Implementierung von Zeichenfolgenliteralen wird dasselbe Problem auftreten, das andere Literale in der Sprache haben: Welcher Typ sie darstellen, hängt davon ab, wie sie verwendet werden. C# stellt ein Literalsuffix bereit, um die Bedeutung für andere Literale eindeutig zu machen. Entwickler können beispielsweise 3.14f schreiben, um einen float-Wert zu erzwingen, oder 1l, um einen long-Wert zu erzwingen.

Ungelöste Fragen

Die ersten drei Designfragen beziehen sich auf Konvertierungen von Zeichenfolgen in Span<byte> / ReadOnlySpan<byte>. Diese wurden nicht implementiert.

(Gelöst) Konvertierungen zwischen einer string-Konstante mit null-Wert und byte-Sequenzen

Ob diese Konvertierung unterstützt wird und, falls ja, wie sie durchgeführt wird, wird nicht angegeben.

Vorschlag:

Implizite Konvertierungen von einer string-Konstanten mit null-Wert in byte[], Span<byte> und ReadOnlySpan<byte> zulassen. Das Ergebnis der Konvertierung ist der default-Wert des Zieltyps.

Lösung:

Der Vorschlag wird genehmigt – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversions-from-null-literals.

(Gelöst) Wohin gehört string_constant_to_UTF8_byte_representation_conversion?

Ist string_constant_to_UTF8_byte_representation_conversion ein eigener Aufzählungspunkt im Abschnitt §10.2 zu impliziten Konvertierungen, oder ist es Teil von §10.2.11, oder gehört es zu einer anderen vorhandenen Gruppe impliziter Konvertierungen?

Vorschlag:

Es handelt sich um einen neuen Aufzählungspunkt im Abschnitt § 10.2 zu impliziten Konvertierungen, ähnlich wie „Implicit interpolated string conversions“ (Implizite Konvertierungen von interpolierten Zeichenfolgen) oder „Method group conversions“ (Methodengruppenkonvertierungen). Es scheint nicht zu den "Konvertierungen von impliziten konstanten Ausdrücken" zu gehören, weil die Quelle zwar ein konstanter Ausdruck ist, das Ergebnis jedoch niemals ein konstanter Ausdruck ist. Außerdem gelten implizite Konvertierungen von konstanten Ausdrücken („Implicit constant expression conversions“) als standardmäßige implizite Konvertierungen („Standard implicit conversions“) gemäß §10.4.2, was wahrscheinlich zu nicht unerheblichen Verhaltensänderungen im Zusammenhang mit benutzerdefinierten Konvertierungen führen wird.

Lösung:

Wir werden eine neue Konvertierungsart für Zeichenkettenkonstanten in UTF-8 Bytes einführen – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-kinds

(Gelöst) Ist string_constant_to_UTF8_byte_representation_conversion eine Standardkonvertierung?

Zusätzlich zu „reinen“ Standardkonvertierungen (Standardkonvertierungen sind die vordefinierten Konvertierungen, die im Rahmen einer benutzerdefinierten Konvertierung stattfinden können) behandelt der Compiler auch einige vordefinierte Konvertierungen als „quasi“ standardmäßig. So kann eine implizite Konvertierung einer interpolierten Zeichenfolge beispielsweise im Rahmen einer benutzerdefinierten Konvertierung stattfinden, wenn im Code eine explizite Umwandlung in den Zieltyp enthalten ist. Als ob es sich um eine standardmäßige explizite Konvertierung handelte, obwohl es sich um eine implizite Konvertierung handelt, die nicht ausdrücklich in den Satz der standardmäßigen impliziten oder expliziten Konvertierungen aufgenommen wurde. Zum Beispiel:

class C
{
    static void Main()
    {
        C1 x = $"hello"; // error CS0266: Cannot implicitly convert type 'string' to 'C1'. An explicit conversion exists (are you missing a cast?)
        var y = (C1)$"dog"; // works
    }
}

class C1
{
    public static implicit operator C1(System.FormattableString x) => new C1();
}

Vorschlag:

Die neue Konvertierung ist keine Standardkonvertierung. Dadurch werden gravierende Verhaltensänderungen bei benutzerdefinierten Konvertierungen vermieden. So müssen wir uns beispielsweise keine Gedanken über benutzerdefinierte Konvertierungen unter impliziten Konvertierungen von Tupelliteralen usw. machen.

Lösung:

Vorerst keine Standardkonvertierung – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#implicit-standard-conversion.

(Gelöst) Konvertierung von Linq-Ausdrucksbaumstruktur

Sollte string_constant_to_UTF8_byte_representation_conversion im Kontext der Konvertierung einer Linq-Ausdrucksbaumstruktur zulässig sein? Wir können dies vorerst verbieten oder einfach die „reduzierte“ Form in die Baumstruktur aufnehmen. Zum Beispiel:

Expression<Func<byte[]>> x = () => "hello";           // () => new [] {104, 101, 108, 108, 111}
Expression<FuncSpanOfByte> y = () => "dog";           // () => new Span`1(new [] {100, 111, 103}) 
Expression<FuncReadOnlySpanOfByte> z = () => "cat";   // () => new ReadOnlySpan`1(new [] {99, 97, 116})

Was ist mit Zeichenfolgenliteralen mit dem Suffix u8? Wir könnten diese als Bytearray-Erstellungen darstellen.

Expression<Func<byte[]>> x = () => "hello"u8;           // () => new [] {104, 101, 108, 108, 111}

Lösung:

In Linq-Ausdrucksbaumstrukturen verbieten – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#expression-tree-representation.

(Gelöst) Der natürliche Typ eines Zeichenfolgenliterals mit dem Suffix u8

Im Abschnitt „Detailliertes Design“ heißt es: „Der natürliche Typ wird allerdings ReadOnlySpan<byte> sein.“ Gleichzeitig: „Bei Verwendung des Suffixes u8 kann das Literal weiterhin in einen der zulässigen Typen konvertiert werden: byte[], Span<byte> oder ReadOnlySpan<byte>.“

Bei diesem Ansatz gibt es mehrere Nachteile:

  • ReadOnlySpan<byte> ist im Desktopframework nicht verfügbar;
  • Es gibt keine vorhandenen Konvertierungen von ReadOnlySpan<byte> in byte[] oder Span<byte>. Um diese zu unterstützen, müssen wir die Literale wahrscheinlich als Zieltypen behandeln. Sowohl die Sprachregeln als auch die Implementierung werden komplizierter.

Vorschlag:

Der natürliche Typ wird byte[] sein. Er ist in allen Frameworks leicht verfügbar. Übrigens, zur Laufzeit beginnen wir immer mit der Erstellung eines Bytearrays, auch beim ursprünglichen Vorschlag. Wir benötigen auch keine speziellen Umwandlungsregeln, um die Konvertierungen nach Span<byte> und ReadOnlySpan<byte>zu unterstützen. Es gibt bereits implizite benutzerdefinierte Konvertierungen von byte[] zu Span<byte> und ReadOnlySpan<byte>. Es gibt sogar eine implizite benutzerdefinierte Konvertierung in ReadOnlyMemory<byte> (siehe Frage zur „Tiefe der Konvertierung“ unten). Es gibt einen Nachteil: Die Sprache lässt keine Verkettung von benutzerdefinierten Konvertierungen zu. Der folgende Code wird also nicht kompiliert:

using System;
class C
{
    static void Main()
    {
        var y = (C2)"dog"u8; // error CS0030: Cannot convert type 'byte[]' to 'C2'
        var z = (C3)"cat"u8; // error CS0030: Cannot convert type 'byte[]' to 'C3'
    }
}

class C2
{
    public static implicit operator C2(Span<byte> x) => new C2();
}

class C3
{
    public static explicit operator C3(ReadOnlySpan<byte> x) => new C3();
}

Wie bei jeder benutzerdefinierten Konvertierung kann jedoch eine explizite Umwandlung verwendet werden, um eine benutzerdefinierte Konvertierung zu einem Teil einer anderen benutzerdefinierten Konvertierung zu machen.

Mit byte[] als natürlichem Typ scheinen alle motivierenden Szenarien gelöst zu werden, die Sprachregeln und die Implementierung werden jedoch erheblich einfacher sein.

Lösung:

Der Vorschlag wird genehmigt – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#natural-type-of-u8-literals. Wir werden wahrscheinlich noch eingehender darüber diskutieren wollen, ob u8-Zeichenfolgenliterale eine Art änderbares Array haben sollen, derzeit halten wir diese Diskussion jedoch nicht für notwendig.

Nur der explizite Konvertierungsoperator wurde implementiert.

(Gelöst) Tiefe der Konvertierung

Funktioniert dies auch überall, wo ein byte[] funktionieren könnte? Berücksichtigen Sie:

static readonly ReadOnlyMemory<byte> s_data1 = "Data"u8;
static readonly ReadOnlyMemory<byte> s_data2 = "Data";

Das erste Beispiel sollte aufgrund des natürlichen Typs, der aus u8 stammt, wahrscheinlich funktionieren.

Das zweite Beispiel ist schwer umzusetzen, da es Konvertierungen in beide Richtungen erfordert. Es sei denn, wir fügen ReadOnlyMemory<byte> als einen der zulässigen Konvertierungstypen hinzu.

Vorschlag:

Machen Sie nichts Besonderes.

Lösung:

Derzeit wurden keine neuen Konvertierungsziele hinzugefügt – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-depth. Keine der Konvertierungen wird kompiliert.

(Gelöst) Unterbrechungen bei der Überladungsauflösung

Die folgende API würde mehrdeutig werden:

M("");
static void M1(ReadOnlySpan<char> charArray) => ...;
static void M1(byte[] byteArray) => ...;

Was sollten wir tun, um dies zu beheben?

Vorschlag:

Ähnlich wie in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#overload-resolution wird das bessere Funktionselement (Better function member §11.6.4.3) aktualisiert, sodass Elemente bevorzugt werden, bei denen keine der beteiligten Konvertierungen eine Konvertierung von string-Konstanten in UTF8-byte-Sequenzen erfordert.

Besseres Funktionselement

... Angesichts einer Argumentliste A mit einer Reihe von Argumentausdrücken {E1, E2, ..., En} und zwei anwendbaren Funktionselementen Mp und Mq mit den Parametertypen {P1, P2, ..., Pn} und {Q1, Q2, ..., Qn} wird Mp als ein besseres Funktionselement als Mq definiert, wenn

  1. die implizite Konvertierung von Ex in Px für jedes Argument keine string_constant_to_UTF8_byte_representation_conversion ist und die implizite Konvertierung von Ex in Qx für mindestens ein Argument eine string_constant_to_UTF8_byte_representation_conversion ist, oder
  2. die implizite Konvertierung von Ex in Px für jedes Argument keine function_type_conversion ist, und
    • Mp eine nicht generische Methode ist oder Mp eine generische Methode mit Typparametern {X1, X2, ..., Xp} ist und das Typargument für jeden Typparameter Xi aus einem Ausdruck oder einem anderen Typ als einem Funktionstyp abgeleitet wird, und
    • die implizite Konvertierung von Ex in Qx für mindestens ein Argument eine function_type_conversion ist oder Mq eine generische Methode mit den Typparametern {Y1, Y2, ..., Yq} ist und das Typargument für mindestens einen Typparameter Yi von einem function_type abgeleitet wird, oder
  3. für jedes Argument ist die implizite Konvertierung von Ex in Qx nicht besser als die implizite Konvertierung von Ex in Px, und für mindestens ein Argument ist die Konvertierung von Ex zu Px besser als die Konvertierung von Ex in Qx.

Beachten Sie, dass das Hinzufügen dieser Regel keine Szenarien abdeckt, in denen Instanzmethoden anwendbar werden und Erweiterungsmethoden verdrängen. Zum Beispiel:

using System;

class Program
{
    static void Main()
    {
        var p = new Program();
        Console.WriteLine(p.M(""));
    }

    public string M(byte[] b) => "byte[]";
}

static class E
{
    public static string M(this object o, string s) => "string";
}

Das Verhalten dieses Codes ändert sich stillschweigend von der Ausgabe von „string“ zur Ausgabe von „byte[]“.

Sind wir mit dieser Verhaltensänderung ok? Sollte es als eine bahnbrechende Änderung dokumentiert werden?

Beachten Sie, dass es keinen Vorschlag gibt, string_constant_to_UTF8_byte_representation_conversion nicht verfügbar zu machen, wenn die C#10-Sprachversion anvisiert wird. In diesem Fall wird das obige Beispiel zu einem Fehler, anstatt in das C#10-Verhalten zurückzukehren. Dies folgt einem allgemeinen Prinzip, dass die Version in der Zielsprache die Semantik der Sprache nicht beeinflusst.

Sind wir mit diesem Verhalten ok? Sollte es als eine bahnbrechende Änderung dokumentiert werden?

Die neue Regel wird auch keine Unterbrechungen im Zusammenhang mit Konvertierungen von Tupelliteralen verhindern. Beispiel:

class C
{
    static void Main()
    {
        System.Console.Write(Test(("s", 1)));
    }

    static string Test((object, int) a) => "object";
    static string Test((byte[], int) a) => "array";
}

wird stillschweigend „array“ anstelle von „object“ ausgeben.

Sind wir mit diesem Verhalten ok? Sollte es als eine bahnbrechende Änderung dokumentiert werden? Vielleicht könnten wir die neue Regel etwas komplizierter machen, um uns mit den Konvertierungen von Tupelliteralen zu befassen.

Lösung:

Der Prototyp wird hier keine Regeln anpassen, sodass wir hoffentlich sehen können, wo es in der Praxis zu Unterbrechungen kommt – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#breaking-changes.

(Gelöst) Sollte beim Suffix u8 die Groß-/Kleinschreibung nicht berücksichtigt werden?

Vorschlag:

Aus Gründen der Konsistenz mit numerischen Suffixen auch das Suffix U8 unterstützen.

Lösung:

Genehmigt – https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#suffix-case-sensitivity.

Beispiele heute

Beispiele dafür, wo die Laufzeit die UTF8-Bytes heute manuell codiert hat

Beispiele, in denen perf in der Tabelle verbleibt

Designbesprechungen

https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-18.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-06.md