次の方法で共有


Utf8 文字列リテラル

手記

この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

概要

この提案では、C# で UTF8 文字列リテラルを記述する機能が追加され、UTF-8 byte 表現に自動的にエンコードされます。

モチベーション

UTF8 は Web の言語であり、.NET スタックの重要な部分でその使用が必要です。 データの多くはネットワーク スタックから byte[] の形式で提供されますが、コードには定数の重要な使用が依然として存在します。 たとえば、ネットワーク スタックでは、一般的に、"HTTP/1.0\r\n"" AUTH"、などの定数を記述する必要があります。 "Content-Length: "

現在、C# は UTF16 エンコードを使用するすべての文字列を表すので、これを行うための効率的な構文はありません。 つまり、開発者は、実行時のエンコードによる利便性を選ぶか、またはバイトを手動で変換し 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 "));

このトレードオフは、ランタイム、ASP.NET、Azure のパートナーにとって頻繁に発生する問題です。 多くの場合、byte[] エンコーディングを手動で書き出すという面倒な作業をしたくないため、パフォーマンスが無視されます。

これを修正するには、言語で UTF8 リテラルを使用し、コンパイル時に UTF8 byte[] にエンコードします。

詳細な設計

文字列リテラルの u8 サフィックス

この言語では、文字列リテラルに u8 サフィックスが提供され、型が UTF8 に強制されます。 サフィックスは大文字と小文字を区別せず、U8 サフィックスはサポートされ、u8 サフィックスと同じ意味を持ちます。

u8 サフィックスを使用する場合、リテラルの値は、文字列の UTF-8 バイト表現を含む ReadOnlySpan<byte> です。 null 終端記号は、呼び出しが null で終了する文字列を想定するいくつかの相互運用シナリオを処理するために、メモリ内の最後のバイト (および ReadOnlySpan<byte>の長さの外側) を超えて配置されます。

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>'.

リテラルはグローバル定数として割り当てられるため、結果として得られる ReadOnlySpan<byte> の有効期間は、それが返されたり他の場所に渡されたりすることを妨げません。 ただし、特に非同期関数内の特定のコンテキストでは、ref 構造体型のローカルが許可されないため、このような状況では、ToArray() 呼び出しや同様の呼び出しが必要になるため、使用がペナルティになります。

u8 リテラルには定数値がありません。 これは、ReadOnlySpan<byte> が今日の定数の型ではできないためです。 const の定義を将来拡張して ReadOnlySpan<byte>を検討する場合は、この値も定数と見なす必要があります。 これは実際には、省略可能なパラメーターの既定値として u8 リテラルを使用できないことを意味します。

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

リテラルの入力テキストが形式が正しくない UTF16 文字列である場合、言語によってエラーが出力されます。

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.

加算演算子

§12.10.5 加算演算子 に次のように新しい箇条書きが追加されます。

  • UTF8 バイト表現の連結:

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

    このバイナリ + 演算子はバイト シーケンス連結を実行し、両方のオペランドがセマンティック UTF8 バイト表現である場合にのみ適用されます。 オペランドは、u8 リテラルの値、または UTF8 バイト表現連結演算子によって生成される値である場合、意味的には UTF8 バイト表現です。

    UTF8 バイト表現連結の結果は、左オペランドのバイトと右オペランドのバイトで構成される ReadOnlySpan<byte> です。 null 終端記号は、呼び出しが null で終了する文字列を想定するいくつかの相互運用シナリオを処理するために、メモリ内の最後のバイト (および ReadOnlySpan<byte>の長さの外側) を超えて配置されます。

引き下げ

言語は、開発者が結果の byte[] リテラルをコードに入力した場合とまったく同じように、UTF8 でエンコードされた文字列を減らします。 例えば:

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.

つまり、new byte[] { ... } 形式に適用されるすべての最適化は、utf8 リテラルにも適用されます。 これは、C# によって PE ファイルの .data セクションに格納されるよう最適化されるため、呼び出しサイトはメモリ割り当てが不要になります。

UTF8 バイト表現連結演算子の複数の連続する適用は、最終的なバイトシーケンスを含むバイト配列を持つ ReadOnlySpan<byte> の 1 つの作成に統合されます。

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.

欠点

コア API に依存する

コンパイラの実装では、無効な文字列検出と byte[]への変換の両方に UTF8Encoding が使用されます。 正確な API は、コンパイラが使用しているターゲット フレームワークによって異なります。 しかし、UTF8Encoding 実装の役馬になります。

これまで、コンパイラはリテラル処理にランタイム API を使用しないようにしてきました。 これは、言語からランタイムへの定数の処理方法を制御するためです。 具体的には、バグ修正などの項目は定数エンコードを変更する可能性があり、C# コンパイルの結果はコンパイラが実行しているランタイムによって異なっていることを意味します。

これは架空の問題ではありません。 Roslyn の初期バージョンでは、浮動小数点定数解析を処理するために double.Parse が使用されています。 その結果、多くの問題が発生しました。 最初に、ネイティブ コンパイラと Roslyn の間で一部の浮動小数点値の表現が異なっていることを意味しました。 2 つ目は、.NET Core が進化し、double.Parse コードの長年のバグを修正した結果、コンパイラが実行したランタイムに応じて言語でこれらの定数の意味が変わることを意味しました。 その結果、コンパイラは独自のバージョンの浮動小数点解析コードを記述し、double.Parseへの依存関係を削除しました。

このシナリオはランタイムチームと議論し、以前に直面したのと同じ問題があるとは考えていません。 UTF8 解析はランタイム間で安定しており、この領域には将来互換性の問題が発生する領域である既知の問題はありません。 実現した場合は、戦略を再評価できます。

選択肢

ターゲット型のみ

この設計では、ターゲット型の指定のみに依存して、string リテラルの u8 サフィックスを削除することができます。 今日のほとんどの場合、string リテラルは ReadOnlySpan<byte> に直接割り当てられているため、不要です。

ReadOnlySpan<byte> span = "Hello World;" 

u8 サフィックスは、主に、var とオーバーロードの解決という 2 つのシナリオをサポートするために存在します。 後者の場合は、次のユース ケースを検討してください。

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

実装を考えると、Write(ReadOnlySpan<byte>) を呼び出す方が良く、u8 サフィックスを使用すると便利です:Write("hello"u8). それがないと、開発者は厄介な型変換 Write((ReadOnlySpan<byte>)"hello")に頼らざるを得ない。

それでもこれは便利な項目であり、この機能なしでは存在でき、後で追加することは重大な問題はありません。

Utf8String 型を待機

現在、.NET エコシステムは、ReadOnlySpan<byte> でデファクト Utf8 文字列型として標準化されていますが、ランタイムが実際の Utf8String 型を導入する可能性があります。

この変更の可能性に直面して、ここで設計を評価し、行った決定を悔やむかどうかを振り返る必要があります。 現実的な確率に基づいて Utf8Stringを導入する必要があるかもしれませんが、その確率は、ReadOnlySpan<byte> が許容可能な代替手段として見つかる日が増えるにつれて、毎日減少しているようです。

文字列リテラルと ReadOnlySpan<byte>の間のターゲット型変換を残念に思う可能性は低いようです。 utf8としての ReadOnlySpan<byte> の使用は現在APIに埋め込まれています。そのため、Utf8String が来て"より良い"型であっても、変換には価値があります。 言語は単に ReadOnlySpan<byte>よりも Utf8String への変換を好む可能性があります。

Utf8Stringの代わりに ReadOnlySpan<byte> を指す u8 サフィックスを残念に思う可能性が高いようです。 stackalloc int[]Span<int>の代わりに自然なタイプの int* を持っていることを私たちが残念に思うのと似ています. しかし、これは取引のブレーカーではなく、不便です。

string 定数と byte シーケンス間の変換

このセクションの変換は実装されていません。 これらの変換はアクティブな提案のままです。

この言語では、テキストが同等の UTF8 バイト表現に変換される string 定数と byte シーケンス間の変換が可能になります。 具体的には、コンパイラでは string_constant_to_UTF8_byte_representation_conversion (string 定数から byte[]Span<byte>、および ReadOnlySpan<byte> への暗黙的な変換) が許可されます。 暗黙的な変換 §10.2 セクションに新しい箇条書きが追加されます。 この変換は§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 }

変換の入力テキストが形式が正しくない UTF16 文字列の場合、言語によってエラーが出力されます。

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

この機能の主な使用法はリテラルと共にあると予想されますが、任意の string 定数値で動作します。 null 値を持つ string 定数からの変換もサポートされます。 変換の結果は、ターゲット型の default 値になります。

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

文字列に対する定数操作(例: +)の場合、エンコードは個々の部分ごとにではなく、最終的に string でまとめて UTF8 に行われます。 変換が成功したかどうかに影響を与える可能性があるため、この順序付けを考慮することが重要です。

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

ここでの 2 つの部分は、サロゲート ペアの不完全な部分であるため、単独では無効です。 個別に UTF8 への正しい翻訳はありませんが、一緒に完全なサロゲート ペアを形成し、UTF8 に正常に変換できます。

Linq 式ツリーでは string_constant_to_UTF8_byte_representation_conversion が許可されていません。

これらの変換への入力は定数であり、データはコンパイル時に完全にエンコードされますが、変換は言語によって定数と見なされません。 今日、配列は一定ではないためです。 const の定義が将来配列を検討するように拡張される場合は、これらの変換も考慮する必要があります。 これは実際には、これらの変換の結果を省略可能なパラメーターの既定値として使用できないことを意味します。

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

実装された文字列リテラルには、言語内の他のリテラルと同じ問題があります。それらが表す型は、その使用方法によって異なります。 C# には、他のリテラルの意味を明確にするためのリテラル サフィックスが用意されています。 たとえば、開発者は 3.14f を記述して値を強制的に float または 1l にして、値を longにすることができます。

未解決の質問

最初の 3 つの設計の質問は、文字列と Span<byte> / ReadOnlySpan<byte> 変換に関連しています。これらは実装されていません。

(解決済み)null 値を持つ string 定数と byte シーケンス間の変換

この変換がサポートされているかどうか、およびサポートされている場合は、その実行方法は指定されません。

提案:

null 値を持つ string 定数から byte[]Span<byte>、および ReadOnlySpan<byte>への暗黙的な変換を許可します。 変換の結果は、ターゲット型の default 値です。

解決方法:

提案は承認されています - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversions-from-null-literals.

(解決済み)string_constant_to_UTF8_byte_representation_conversion はどこに属していますか?

string_constant_to_UTF8_byte_representation_conversion は、暗黙的変換 §10.2 セクション自体の箇条書きですか、それとも §10.2.11 の一部ですか、あるいは他の既存の暗黙的な変換グループに属していますか?

提案:

§10.2の暗黙的な変換に新しい箇条書きが追加されました。この箇条書きは、「暗黙的な挿入文字列変換」や「メソッドグループ変換」と似ています。 ソースが定数式であっても、結果が定数式になることはないため、"暗黙的な定数式変換" に属しているようには感じられません。 また、"暗黙的な定数式変換" は§10.4.2"標準の暗黙的な変換" と見なされ、ユーザー定義の変換を伴う非単純な動作の変更につながる可能性があります。

解決方法:

文字列定数から UTF-8 バイトへの新しい変換の種類を導入します - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-kinds

(解決済み) string_constant_to_UTF8_byte_representation_conversion は標準変換ですか

"純粋な" 標準変換 (標準変換は、ユーザー定義変換の一部として発生する可能性のある事前定義済みの変換) に加えて、コンパイラでは、いくつかの定義済みの変換も "やや" 標準として扱われます。 たとえば、暗黙的な補間文字列変換は、コード中でターゲット型への明示的なキャストが行われると、ユーザー定義の変換として発生することがあります。 暗黙的な変換でありながら、まるで標準の明示的な変換であるかのように振る舞いますが、それは標準の暗黙的または明示的な変換のセットに明示的に含まれているわけではありません。 例えば:

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

提案:

新しい変換は標準変換ではありません。 これにより、ユーザー定義の変換に関連する単純でない動作の変更を回避できます。 たとえば、暗黙のタプル リテラル変換におけるユーザー定義の変換について心配する必要はありません。

解決方法:

現時点では、標準の変換ではありません - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#implicit-standard-conversion

(解決済み)Linq 式ツリーの変換

Linq 式ツリー変換のコンテキストにおいて、string_constant_to_UTF8_byte_representation_conversion を許可すべきでしょうか? 今のところそれを許可しないという選択もできますし、「引き下げた」フォームをツリーに単純に含めることもできます。 例えば:

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

u8 サフィックスを持つ文字列リテラルはどうですか? これらはバイト配列の作成として表示できます。

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

解決方法:

Linq 式ツリーで許可しない - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#expression-tree-representation

(解決済み) u8 サフィックスを持つ文字列リテラルの自然なタイプ

「詳細なデザイン」セクションには、「自然なタイプは ReadOnlySpan<byte>されます。」同時に、「u8 サフィックスが使用されている場合、リテラルは引き続き許可されている任意の型 (byte[]Span<byte>、または ReadOnlySpan<byte>) に変換できます。

この方法にはいくつかの欠点があります。

  • ReadOnlySpan<byte> はデスクトップ フレームワークでは使用できません。
  • ReadOnlySpan<byte> から byte[] または Span<byte>への既存の変換はありません。 それらをサポートするには、リテラルをターゲット型として扱う必要があります。 言語規則と実装の両方が複雑になります。

提案:

自然タイプは byte[]です。 すべてのフレームワークですぐに使用できます。 ところで、実行時には、元の提案であっても、常にバイト配列の作成から始めます。 また、Span<byte>ReadOnlySpan<byte>への変換をサポートするために特別な変換規則は必要ありません。 byte[] から Span<byte> および ReadOnlySpan<byte>への暗黙的なユーザー定義変換は既に存在します。 ReadOnlyMemory<byte> への暗黙的なユーザー定義変換もあります (以下の「変換の深さ」の質問を参照してください)。 欠点があります。言語では、ユーザー定義の変換をチェーンすることはできません。 そのため、次のコードはコンパイルされません。

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

ただし、ユーザー定義の変換と同様に、明示的なキャストを使用して、1 つのユーザー定義変換を別のユーザー定義変換の一部にすることができます。

すべての動機付けシナリオが自然な型として byte[] で対処されるように感じますが、言語の規則と実装は大幅に簡単になります。

解決方法:

提案は承認されています - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#natural-type-of-u8-literals. 文字列リテラルが変更可能な配列の型を持つ必要 u8 があるかどうかについて、より深く議論したいと思うでしょうが、現時点では議論は必要ないと考えています。

明示的な変換演算子のみが実装されています。

(解決済み)変換の深さ

また、byte[] が機能する可能性のある任意の場所でも機能しますか? 考慮してください:

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

最初の例は、u8に由来する自然な性質によって動作するはずです。

2 番目の例では、双方向の変換が必要なため、作業が困難です。 これは、許可される変換の種類の 1 つとして ReadOnlyMemory<byte> を追加しない限りです。

提案:

特別なことをしないでください。

解決方法:

現時点では、新しい変換ターゲットは追加されていません https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-depth。 どちらの変換もコンパイルされません。

(解決済み)オーバーロード解決の失敗

次の API はあいまいになります。

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

これに対処するにはどうすればよいでしょうか。

提案:

https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#overload-resolutionと同様に、Better 関数メンバー (§11.6.4.3) が更新され、関連する変換で string 定数を UTF8 byte シーケンスに変換する必要がないメンバーが優先されます。

ベター関数メンバー

...引数リスト A 一連の引数式が {E1, E2, ..., En} され、2 つの適用可能な関数メンバーが Mp され、パラメーター型が {P1, P2, ..., Pn} および {Q1, Q2, ..., Qn}を持つ Mq がある場合、Mp は、Mq よりも 優れた関数メンバー として定義されます。

  1. 各引数について、Ex から Px への暗黙的な変換は string_constant_to_UTF8_byte_representation_conversion ではありません。また、少なくとも 1 つの引数について、Ex から Qx への暗黙的な変換は string_constant_to_UTF8_byte_representation_conversion または です。
  2. 引数ごとに、Ex から Px への暗黙の変換は の関数型変換ではありませんが、
    • Mp は非ジェネリック メソッドであり、または型パラメータ {X1, X2, ..., Xp} を持つジェネリック メソッド Mp です。型引数は、各型パラメータ Xi に対して、式から、または 関数型ではない型から推論されます。
    • 少なくとも 1 つの引数に対して、Ex から Qx への暗黙的な変換が function_type_conversionの場合、または Mq{Y1, Y2, ..., Yq} の型パラメーターを持つジェネリックメソッドであり、さらに少なくとも 1 つの型パラメーター Yi に対して、型引数が function_typeから推論される場合。
  3. 各引数に対して、Ex から Qx への暗黙的な変換は、Ex から Pxへの暗黙的な変換よりも適していません。少なくとも 1 つの引数の場合、Ex から Px への変換は、Ex から Qxへの変換よりも優れています。

この規則の追加では、インスタンス メソッドが適用され、拡張メソッドが "シャドウ" されるシナリオはカバーされないことに注意してください。 例えば:

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

このコードの動作は、"string" の印刷から "byte[]" の印刷に自動的に変わります。

この動作の変更で問題ありませんか? 重大な変更として文書化する必要がありますか?

C#10 言語バージョンが対象の場合、string_constant_to_UTF8_byte_representation_conversion を使用できないようにする提案はありません。 その場合、上記の例は C#10 の動作に戻るのではなく、エラーになります。 これは、ターゲット言語バージョンが言語のセマンティクスに影響を与えないという一般的な原則に従います。

この動作で問題ありませんか? 重大な変更として文書化する必要がありますか?

新しい規則でも、タプルリテラル変換を伴う中断を防ぐことはできません。 例えば

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

上記は、"object" ではなく "array" をサイレントで出力します。

この動作で問題ありませんか? 重大な変更として文書化する必要がありますか? おそらく、タプルリテラル変換を掘り下げるために新しいルールを複雑にすることができます。

解決方法:

プロトタイプはここでルールを調整しないため、実際に何が壊れるかを見ることができます - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#breaking-changes.

(解決済み) サフィックス u8 は大文字と小文字を区別せずに扱うべきですか?

提案:

U8 サフィックスをサポートし、数値サフィックスとの一貫性を確保します。

解決方法:

承認済み - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#suffix-case-sensitivity

今日の例

現在、ランタイムが UTF8 バイトを手動でエンコードした例

パフォーマンスを棚上げする例

デザイン会議

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