Literały łańcuchów znaków UTF-8
Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są zanotowane w odnośnych notatkach ze spotkań dotyczących projektowania języka (LDM).
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Problem z czempionem: https://github.com/dotnet/csharplang/issues/184
Streszczenie
Ta propozycja dodaje możliwość pisania literałów ciągów UTF8 w języku C# i automatycznego kodowania w ich reprezentacji UTF-8 byte
.
Motywacja
UTF8 to język sieci Web, a jego użycie jest niezbędne w znaczących częściach stosu platformy .NET. Podczas gdy wiele danych jest w postaci byte[]
poza stosem sieciowym nadal istnieją znaczące zastosowania stałych w kodzie. Na przykład stos sieciowy musi często zapisywać stałe, takie jak "HTTP/1.0\r\n"
, " AUTH"
lub .
"Content-Length: "
.
Obecnie nie ma wydajnej składni w tym celu, ponieważ język C# reprezentuje wszystkie ciągi przy użyciu kodowania UTF16. Oznacza to, że deweloperzy muszą wybrać między wygodą kodowania podczas działania, co wiąże się z obciążeniem, w tym czasem spędzonym na faktyczne przeprowadzenie operacji kodowania podczas uruchamiania (oraz alokacjami, jeśli dotyczy typu, który ich nie wymaga), a ręcznym przeliczaniem bajtów i przechowywaniem w byte[]
.
// 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 "));
Ten kompromis jest punktem bólu, który często pojawia się dla naszych partnerów w środowisku uruchomieniowym, ASP.NET i Azure. Często prowadzi to do niewykorzystania całkowitego potencjału, ponieważ nie chcą przechodzić przez trudności związane z ręcznym zapisywaniem kodowania byte[]
.
Aby rozwiązać ten problem, będziemy zezwalać na literały UTF8 w języku i kodować je do byte[]
UTF8 w czasie kompilacji.
Szczegółowy projekt
u8
sufiks literałów ciągu
Język doda sufiks u8
do literałów tekstowych, aby wymusić użycie kodowania UTF8.
Sufiks nie jest wrażliwy na wielkość liter, sufiks U8
będzie obsługiwany i będzie miał takie samo znaczenie jak sufiks u8
.
Gdy jest używany sufiks u8
, wartość literału to ReadOnlySpan<byte>
zawierające reprezentację ciągu w postaci bajtów UTF-8.
Terminator o wartości null jest umieszczany po ostatnim bajcie w pamięci oraz poza długością ReadOnlySpan<byte>
, aby obsługiwać niektóre scenariusze interoperacyjności, w których wywołanie oczekuje ciągów znakowych zakończonych znakiem null.
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>'.
Ponieważ literały byłyby przydzielane jako stałe globalne, czas życia wynikowego ReadOnlySpan<byte>
nie uniemożliwiłby jego zwracania ani przekazywania do innych lokalizacji. Jednak niektóre konteksty, zwłaszcza w funkcjach asynchronicznych, nie pozwalają na deklarowanie lokalnych zmiennych dla typów struktur ref, więc w tych sytuacjach wystąpi koszt użycia, wymagane będzie wywołanie ToArray()
lub podobne.
Literał u8
nie ma stałej wartości. Wynika to z faktu, że ReadOnlySpan<byte>
nie może być typem stałej dzisiaj. Jeśli definicja const
zostanie rozszerzona w przyszłości, aby rozważyć ReadOnlySpan<byte>
, ta wartość powinna być również traktowana jako stała. Praktycznie oznacza to, że literał u8
nie może być używany jako wartość domyślna opcjonalnego parametru.
// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing"u8) { ... }
Gdy tekst wejściowy literału jest źle sformułowanym ciągiem UTF16, język będzie emitować błąd:
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.
Operator dodawania
Nowy punktor zostanie dodany do §12.10.5 Operator dodawania w następujący sposób.
Łączenie reprezentacji bajtów UTF8:
ReadOnlySpan<byte> operator +(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y);
Ten operator
+
binarny wykonuje łączenie sekwencji bajtów i ma zastosowanie tylko wtedy, gdy oba operandy są semantycznie reprezentacjami bajtów UTF8. Operand jest semantycznie reprezentacją bajtów UTF8, gdy jest to wartość literałuu8
lub wartość wygenerowana przez operator łączenia reprezentacji bajtów UTF8.Wynikiem łączenia reprezentacji bajtów UTF8 jest
ReadOnlySpan<byte>
, który składa się z bajtów lewego operandu, a następnie bajtów prawego operandu. Terminator o wartości null jest umieszczany po ostatnim bajcie w pamięci oraz poza długościąReadOnlySpan<byte>
, aby obsługiwać niektóre scenariusze interoperacyjności, w których wywołanie oczekuje ciągów znakowych zakończonych znakiem null.
Obniżenie
Język obniży zakodowane ciągi UTF8 dokładnie tak, jakby deweloper wpisał wynikowy literał byte[]
w kodzie. Na przykład:
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.
Oznacza to, że wszystkie optymalizacje stosowane do formularza new byte[] { ... }
będą miały zastosowanie również do literałów utf8. Oznacza to, że miejsce wywołania nie będzie wymagać alokacji, ponieważ C# zoptymalizuje to, aby być przechowywane w sekcji .data
pliku PE.
Wiele kolejnych zastosowań operatorów łączenia bajtów w reprezentacji UTF8 jest zredukowanych do pojedynczego utworzenia ReadOnlySpan<byte>
z tablicą bajtów zawierającą końcową sekwencję bajtów.
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.
Wady
Poleganie na podstawowych interfejsach API
Implementacja kompilatora będzie używać UTF8Encoding
zarówno do wykrywania nieprawidłowych ciągów, jak i do tłumaczenia na byte[]
. Dokładne interfejsy API będą prawdopodobnie zależeć od platformy docelowej używanej przez kompilator. Ale UTF8Encoding
będzie główną siłą napędową implementacji.
W przeszłości kompilator unikał używania interfejsów API środowiska uruchomieniowego do przetwarzania literału. Wynika to z faktu, że przejmuje kontrolę nad tym, jak stałe są przetwarzane z dala od języka i do środowiska uruchomieniowego. Konkretnie oznacza to, że elementy, takie jak poprawki błędów, mogą zmieniać stałe kodowanie i oznaczać, że wynik kompilacji języka C# zależy od tego, na którym środowisku uruchomieniowym jest wykonywany kompilator.
Nie jest to hipotetyczny problem. Wczesne wersje platformy Roslyn używały double.Parse
do obsługi analizowania stałych zmiennoprzecinkowych. Spowodowało to szereg problemów. Najpierw oznaczało to, że niektóre wartości zmiennoprzecinkowe miały różne reprezentacje między natywnym kompilatorem a Roslyn. Po drugie, gdy platforma .NET Core ewoluowała i naprawiła długotrwałe błędy w kodzie double.Parse
oznaczało, że znaczenie tych stałych zmieniło się w języku w zależności od tego, na jakim środowisku uruchomieniowym jest wykonywany kompilator. W rezultacie kompilator napisał własną wersję kodu analizy zmiennoprzecinkowych i usunął zależność od double.Parse
.
Ten scenariusz został omówiony z zespołem odpowiedzialnym za środowisko uruchomieniowe i nie uważamy, że ma te same problemy, które napotkaliśmy wcześniej. Parsowanie UTF8 jest stabilne w różnych środowiskach uruchomieniowych i nie ma znanych problemów w tym obszarze, które mogłyby stanowić problemy ze zgodnością w przyszłości. Jeśli tak się stanie, możemy ponownie ocenić strategię.
Alternatywy
Tylko typ docelowy
Projekt może polegać tylko na typowaniu docelowym i usuwać sufiks u8
z literałów string
. W większości przypadków literał string
jest przypisywany bezpośrednio do ReadOnlySpan<byte>
, dlatego jest to niepotrzebne.
ReadOnlySpan<byte> span = "Hello World;"
Sufiks u8
istnieje przede wszystkim do obsługi dwóch scenariuszy: var
i rozpoznawania przeciążeń. W przypadku tego drugiego należy wziąć pod uwagę następujący przypadek użycia:
void Write(ReadOnlySpan<byte> span) { ... }
void Write(string s) {
var bytes = Encoding.UTF8.GetBytes(s);
Write(bytes.AsSpan());
}
Biorąc pod uwagę implementację, lepiej wywołać Write(ReadOnlySpan<byte>)
, a sufiks u8
sprawia, że jest to wygodne: Write("hello"u8)
. Kiedy tego brakuje, deweloperzy muszą uciekać się do niezręcznego rzutowania Write((ReadOnlySpan<byte>)"hello")
.
Jest to wciąż element wygody, funkcja może istnieć bez niej, a dodanie jej w późniejszym terminie nie powoduje zakłóceń.
Czekaj na typ Utf8String
Chociaż ekosystem platformy .NET obecnie uznaje ReadOnlySpan<byte>
za domyślny typ ciągu Utf8, istnieje możliwość, że w przyszłości środowisko uruchomieniowe wprowadzi rzeczywisty typ Utf8String
.
Powinniśmy ocenić nasz projekt w obliczu tej możliwej zmiany i zastanowić się, czy żałujemy podjętych decyzji. Należy to jednak rozważyć w stosunku do realistycznego prawdopodobieństwa, że wprowadzimy Utf8String
, przy czym prawdopodobieństwo to wydaje się zmniejszać każdego dnia, kiedy uznajemy ReadOnlySpan<byte>
za akceptowalną alternatywę.
Wydaje się mało prawdopodobne, że żałowalibyśmy konwersji typu docelowego między literałami łańcuchów a ReadOnlySpan<byte>
. Użycie ReadOnlySpan<byte>
jako UTF-8 zostało teraz osadzone w naszych API, i dlatego nadal ma wartość konwersji, nawet jeśli Utf8String
się pojawi i będzie "lepszym" typem. Język może po prostu preferować konwersje na Utf8String
w porównaniu z ReadOnlySpan<byte>
.
Wydaje się bardziej prawdopodobne, że żałujemy sufiksu u8
wskazującego na ReadOnlySpan<byte>
zamiast Utf8String
. Byłoby to podobne do tego, jak żałujemy, że stackalloc int[]
ma naturalny typ int*
zamiast Span<int>
. To nie jest czynnik decydujący, tylko niedogodność.
Konwersje między stałymi string
a sekwencjami byte
Konwersje w tej sekcji nie zostały zaimplementowane. Te przekształcenia pozostają aktywnymi propozycjami.
Język umożliwi konwersje między stałymi string
a sekwencjami byte
, w których tekst jest konwertowany na równoważną reprezentację bajtów UTF8. W szczególności kompilator zezwala na string_constant_to_UTF8_byte_representation_conversion — na niejawne konwersje stałych string
do byte[]
, Span<byte>
i ReadOnlySpan<byte>
.
Do sekcji niejawne konwersje §10.2 zostanie dodany nowy punktor. Ta konwersja nie jest standardową konwersją §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 }
Gdy tekst wejściowy konwersji jest źle sformułowanym ciągiem UTF16, język będzie emitować błąd:
const string text = "hello \uD801\uD802";
byte[] bytes = text; // Error: the input string is not valid UTF16
Oczekuje się, że główne użycie tej funkcji będzie z literałami, ale będzie również działać z dowolną wartością stałą o indeksie string
.
Również będzie obsługiwana konwersja z stałej string
z wartością null
. Wynikiem konwersji będzie default
wartość typu docelowego.
const string data = "dog"
ReadOnlySpan<byte> span = data; // new byte[] { 0x64, 0x6f, 0x67 }
W przypadku każdej stałej operacji na ciągach, takich jak +
, kodowanie do UTF8 będzie wykonywane na końcowym string
, zamiast na poszczególnych częściach i późniejszym łączeniu wyników. Takie uporządkowanie jest ważne, ponieważ może mieć wpływ na to, czy konwersja powiedzie się.
const string first = "\uD83D"; // high surrogate
const string second = "\uDE00"; // low surrogate
ReadOnlySpan<byte> span = first + second;
Elementy te są nieprawidłowe same w sobie, ponieważ są niekompletne częścią pary zastępczej. Indywidualnie nie ma poprawnego tłumaczenia na UTF8, ale razem tworzą pełną parę zastępczą, którą można pomyślnie przetłumaczyć na UTF8.
string_constant_to_UTF8_byte_representation_conversion nie jest dozwolona w drzewach wyrażeń Linq.
Podczas gdy dane wejściowe do tych konwersji są stałe, a dane są w pełni zakodowane w czasie kompilacji, konwersja nie jest uważana za stałą przez język. Wynika to z tego, że tablice nie są obecnie stałe. Jeśli definicja const
zostanie rozszerzona w przyszłości, aby uwzględniać tablice, należy także wziąć pod uwagę te konwersje. Praktycznie oznacza to, że wynik tych konwersji nie może być używany jako wartość domyślna opcjonalnego parametru.
// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing") { ... }
Po implementacji literałów ciągów będą miały ten sam problem, jaki mają inne literały w języku: jaki typ reprezentują, zależy od sposobu ich użycia. Język C# udostępnia sufiks literału do precyzowania znaczenia innych literałów. Na przykład deweloperzy mogą napisać 3.14f
, aby wymusić, że wartość będzie float
lub 1l
, aby wymusić, że wartość będzie long
.
Nierozwiązane pytania
Pierwsze trzy pytania projektowe dotyczą konwersji ciągów na Span<byte>
/ ReadOnlySpan<byte>
. Nie zostały one wdrożone.
(Rozwiązano) Konwersje między stałą string
z wartością null
a sekwencjami byte
Czy ta konwersja jest obsługiwana, a jeśli tak, sposób jej wykonania nie jest określona.
Propozycja:
Zezwalaj na niejawne konwersje ze stałej string
o wartości null
na byte[]
, Span<byte>
i ReadOnlySpan<byte>
. Wynikiem konwersji jest default
wartość typu docelowego.
rozwiązanie:
Wniosek jest zatwierdzony — https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversions-from-null-literals.
(Rozwiązano) Gdzie należy string_constant_to_UTF8_byte_representation_conversion?
Czy string_constant_to_UTF8_byte_representation_conversion jest osobnym punktem w sekcji niejawnych konwersji §10.2, czy stanowi część §10.2.11, czy też należy do innej istniejącej grupy niejawnych konwersji?
Propozycja:
Jest to nowy punkt w niejawnych konwersjach §10.2, podobnie jak "Niejawne konwersje ciągów interpolowanych" lub "Konwersje grup metod". Nie wydaje się, że należy do "Niejawnych konwersji wyrażeń stałych", ponieważ mimo że źródło jest wyrażeniem stałym, wynik nigdy nie jest wyrażeniem stałym. Ponadto "Niejawne konwersje wyrażeń stałych" są uważane za "Standardowe konwersje niejawne" §10.4.2, co może prowadzić do nietrywialnych zmian zachowania obejmujących konwersje zdefiniowane przez użytkownika.
rozwiązanie:
Wprowadzimy nowy rodzaj konwersji dla stałej ciągu do bajtów UTF-8 — https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-kinds
(Rozwiązano) Czy string_constant_to_UTF8_byte_representation_conversion jest konwersją standardową?
Oprócz "czystych" konwersji standardowych (konwersje standardowe to wstępnie zdefiniowane konwersje, które mogą wystąpić w ramach konwersji zdefiniowanej przez użytkownika), kompilator traktuje również niektóre wstępnie zdefiniowane konwersje jako "nieco" standard. Na przykład niejawna konwersja ciągów interpolowanych może wystąpić w ramach konwersji zdefiniowanej przez użytkownika, jeśli istnieje jawne rzutowanie do typu docelowego w kodzie. Tak, jakby była to standardowa jawna konwersja, mimo że jest niejawną konwersją, która nie jest jawnie uwzględniona w zestawie standardowych konwersji niejawnych lub jawnych. Na przykład:
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();
}
Propozycja:
Nowa konwersja nie jest standardową konwersją. Pozwoli to uniknąć nietrywialnych zmian zachowania obejmujących konwersje zdefiniowane przez użytkownika. Na przykład nie będziemy musieli martwić się o konwersje zdefiniowane przez użytkownika w kontekście niejawnych konwersji literałów krotek itp.
rozwiązanie:
Obecnie nie jest to standardowa konwersja — https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#implicit-standard-conversion.
(Rozwiązano) Konwersja drzewa wyrażeń Linq
Czy string_constant_to_UTF8_byte_representation_conversion być dozwolone w kontekście konwersji drzewa wyrażeń Linq? Możemy nie zezwalać na to na razie lub możemy po prostu dołączyć "obniżoną" formę do drzewa. Na przykład:
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})
Co z literałami ciągów z sufiksem u8
? Możemy przedstawić je jako tworzenie tablic bajtów.
Expression<Func<byte[]>> x = () => "hello"u8; // () => new [] {104, 101, 108, 108, 111}
rozwiązanie:
Nie zezwalaj na drzewa wyrażeń Linq — https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#expression-tree-representation.
(Rozwiązano) Naturalny typ literału ciągu z sufiksem u8
Sekcja "Szczegółowy projekt" mówi: "Typ naturalny jednak będzie ReadOnlySpan<byte>
." Jednocześnie, gdy używany jest sufiks u8
, literał może być nadal konwertowany na dowolny z dozwolonych typów: byte[]
, Span<byte>
lub ReadOnlySpan<byte>
.
Istnieje kilka wad tego podejścia:
-
ReadOnlySpan<byte>
nie jest dostępna na platformie desktopowej. - Nie ma istniejących konwersji z
ReadOnlySpan<byte>
nabyte[]
lubSpan<byte>
. Aby je obsługiwać, prawdopodobnie będziemy musieli traktować literały jako typ docelowy. Zarówno reguły języka, jak i implementacja staną się bardziej skomplikowane.
Propozycja:
Typ naturalny będzie byte[]
. Jest on łatwo dostępny na wszystkich platformach. Przy okazji, w czasie wykonywania procesów zawsze zaczniemy od utworzenia tablicy bajtów, nawet przy pierwotnej propozycji. Nie potrzebujemy również żadnych specjalnych reguł konwersji do obsługi konwersji na Span<byte>
i ReadOnlySpan<byte>
. Istnieją już niejawne konwersje zdefiniowane przez użytkownika z byte[]
na Span<byte>
i ReadOnlySpan<byte>
. Istnieje nawet niejawna konwersja zdefiniowana przez użytkownika na ReadOnlyMemory<byte>
(zobacz pytanie "Głębokość konwersji" poniżej). Istnieje wada, język nie zezwala na łączenie łańcuchowych konwersji zdefiniowanych przez użytkownika. Dlatego następujący kod nie zostanie skompilowany:
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();
}
Jednak, podobnie jak w przypadku dowolnej konwersji zdefiniowanej przez użytkownika, jawne rzutowanie może służyć do uczynienia jednej konwersji zdefiniowanej przez użytkownika częścią innej konwersji zdefiniowanej przez użytkownika.
Wydaje się, że wszystkie scenariusze motywacyjne będą rozwiązywane przy użyciu byte[]
jako typu naturalnego, ale zasady języka i implementacja będą znacznie prostsze.
rozwiązanie:
Wniosek jest zatwierdzony — https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#natural-type-of-u8-literals.
Prawdopodobnie będziemy chcieli głębszą debatę nad tym, czy literały ciągu u8
powinny mieć typ modyfikowalnej tablicy, ale uważamy, że taka debata na razie nie jest niezbędna.
Zaimplementowano tylko jawnego operatora konwersji.
(Rozwiązano) Głębokość konwersji
Czy będzie również działać wszędzie tam, gdzie może działać bajt[]? Rozważ:
static readonly ReadOnlyMemory<byte> s_data1 = "Data"u8;
static readonly ReadOnlyMemory<byte> s_data2 = "Data";
Pierwszy przykład prawdopodobnie powinien działać z powodu typu naturalnego pochodzącego z u8
.
Drugi przykład jest trudny w realizacji, ponieważ wymaga konwersji w obu kierunkach. Dzieje się tak, chyba że dodamy ReadOnlyMemory<byte>
jako jeden z dozwolonych typów konwersji.
Propozycja:
Nie rób nic specjalnego.
rozwiązanie:
Nie dodano żadnych nowych obiektów docelowych konwersji na razie https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-depth. Żadna z tych konwersji się nie kompiluje.
(Rozwiązano) Rozwiązywanie przeciążeń
Następujący interfejs API stałby się niejednoznaczny:
M("");
static void M1(ReadOnlySpan<char> charArray) => ...;
static void M1(byte[] byteArray) => ...;
Co powinniśmy zrobić, aby rozwiązać ten problem?
Propozycja:
Podobnie jak https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#overload-resolution, element członkowski funkcji Better (§11.6.4.3) jest aktualizowany, aby preferować elementy członkowskie, w których żadna z konwersji nie wymaga konwersji stałych string
na sekwencje byte
UTF8.
Lepszy członek funkcji
... Biorąc pod uwagę listę argumentów
z zestawem wyrażeń argumentów i dwoma odpowiednimi elementami członkowskimi funkcji i z typami parametrów i , jest definiowana jako funkcji niż lepiej , jeśli
- dla każdego argumentu, niejawna konwersja z
Ex
naPx
nie jest konwersją stałej ciągu na reprezentację bajtową UTF-8, natomiast dla co najmniej jednego argumentu niejawna konwersja zEx
naQx
jest konwersją stałej ciągu na reprezentację bajtową UTF-8lub.- dla każdego argumentu konwersja niejawna z
Ex
naPx
nie jest konwersją typu funkcji i
Mp
jest metodą niegeneryjną lubMp
jest metodą ogólną z parametrami typu{X1, X2, ..., Xp}
i dla każdego parametru typuXi
argument typu jest wnioskowany z wyrażenia lub typu innego niż function_type, i- dla co najmniej jednego argumentu niejawna konwersja z
Ex
naQx
jest function_type_conversionlubMq
jest metodą ogólną z parametrami typu{Y1, Y2, ..., Yq}
i dla co najmniej jednego parametru typuYi
argument typu jest wnioskowany z function_type, lub- dla każdego argumentu niejawna konwersja z
Ex
naQx
nie jest lepsza niż niejawna konwersja zEx
naPx
i dla co najmniej jednego argumentu konwersja zEx
naPx
jest lepsza niż konwersja zEx
naQx
.
Należy pamiętać, że dodanie tej reguły nie obejmuje scenariuszy, w których metody instancyjne stają się stosowalne i przesłaniają metody rozszerzeń. Na przykład:
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";
}
Zachowanie tego kodu w trybie dyskretnym zmieni się z drukowania "ciąg" na drukowanie "bajt[]".
Czy jesteśmy w porządku z tą zmianą zachowania? Czy powinna być udokumentowana jako zmiana powodująca niezgodność?
Należy pamiętać, że nie ma propozycji, aby string_constant_to_UTF8_byte_representation_conversion stało się niedostępne, gdy celowana jest wersja języka C#10. W takim przypadku powyższy przykład staje się błędem zamiast powracać do zachowania języka C#10. Wynika to z ogólnej zasady, że wersja języka docelowego nie ma wpływu na semantyka języka.
Czy jesteśmy w porządku z tym zachowaniem? Czy powinna być udokumentowana jako zmiana powodująca niezgodność?
Również nowa reguła nie zapobiegnie przerwom związanym z konwersjami literałów krotek. Na przykład
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";
}
program będzie dyskretnie drukować "tablica" zamiast "object".
Czy jesteśmy w porządku z tym zachowaniem? Czy powinna być udokumentowana jako zmiana powodująca niezgodność? Być może moglibyśmy skomplikować nową regułę, aby zagłębić się w przekształcenia literałów krotek.
rozwiązanie:
Prototyp nie dostosuje tu żadnych reguł, więc mamy nadzieję, że będziemy mogli zobaczyć, co się zepsuje w praktyce - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#breaking-changes.
(Rozwiązano) Czy sufiks u8
powinien być nieczuły na wielkość liter?
Propozycja:
Wsparcie dla sufiksu U8
w celu zapewnienia zgodności z sufiksami liczbowymi.
rozwiązanie:
Zatwierdzone — https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#suffix-case-sensitivity.
Przykłady dzisiaj
Przykłady, w których środowisko uruchomieniowe koduje ręcznie bajty UTF8 dzisiaj
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/Common/src/System/Net/Http/aspnetcore/Http2/Hpack/StatusCodes.cs#L13-L78
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs#L581-L591
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpResponseStream.Windows.cs#L284
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs#L30
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs#L852
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs#L35-L42
Przykłady pozostawienia wydajności w tabeli
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs#L16-L17
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L37-L43
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs#L78
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs#L669-L687
Spotkania projektowe
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
C# feature specifications