注
この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。
機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。
機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。
チャンピオン号: https://github.com/dotnet/csharplang/issues/9662
まとめ
共用体 は、結合して共用体型の C# サポートを提供する、リンクされた一連の機能です。
-
共用体の型:
[Union]属性を持つ構造体とクラスは 共用体の型として認識され、 共用体の動作をサポートします。 - ケース型: 共用体型には、コンストラクターとファクトリ メソッドにパラメーターによって指定される一連の ケース型があります。
-
共用体の動作: 共用体の型は、次の 共用体の動作をサポートします。
- 共用体変換: 各ケース型から共用体型への暗黙的な 共用体変換 があります。
- 和集合一致: 和集合値に対するパターン マッチングは、その内容を暗黙的に "ラップ解除" し、代わりに基になる値にパターンを適用します。
- 和集合の完全性: すべてのケースの型が一致した場合、フォールバック ケースを必要とせずに、共用体値に対する切り替え式が完全に実行されます。
- 共用体の null 許容: Null 許容分析により、共用体の内容の null 状態の追跡が強化されました。
- 共用体パターン: すべての共用体の型は基本的な 共用体パターンに従いますが、特定のシナリオには追加の省略可能なパターンがあります。
- 共用体宣言: 短縮構文を使用すると、共用体の型を直接宣言できます。 実装は "オピニオン" です。これは、基本的な共用体パターンに従い、内容を 1 つの参照フィールドとして格納する構造体宣言です。
- 共用体インターフェイス: いくつかのインターフェイスが言語によって認識され、共用体宣言の実装で使用されます。
モチベーション
共用体は、長く要求される C# 機能です。これにより、パターン マッチングが信頼できる方法で、閉じた型のセットから値を表現できます。
共用体 の型 と共用体 の宣言 を分離することで、C# は、オピニオンセマンティクスを持つ簡潔な共用体宣言構文を持つだけでなく、既存の型または他の実装の選択肢を使用して共用体の動作を選択することもできます。
C# で提案される共用体は 、型 の共用体であり、"判別" や "タグ付け" ではありません。 "判別共用体" は、新しい型宣言をケース型として使用することで、"型共用体" の観点から表すことができます。 または、完全さに重点を置いた別の関連する今後の C# 機能である クローズド階層として実装することもできます。
詳細な設計
共用体型
System.Runtime.CompilerServices.UnionAttribute属性を持つクラスまたは構造体の型は、共用体型と見なされます。
namespace System.Runtime.CompilerServices
{
[AttributeUsage(Class | Struct, AllowMultiple = false)]
public class UnionAttribute : Attribute;
}
共用体の型は、パブリック 共用体メンバーの特定のパターンに従う必要があります。共用体の型自体で宣言するか、"共用体メンバー プロバイダー" に委任する必要があります。
一部の共用体メンバーは必須であり、他のメンバーは省略可能です。
共用体型には、特定の共用体メンバーの署名に基づいて確立される一連の ケース・タイプ があります。
共用体値の内容には、 Value プロパティを使用してアクセスできます。 この言語では、 Value ケースの種類の 1 つ、または null の値のみが含まれていることを前提としています ( 整形式を参照)。
共用体メンバー プロバイダー
既定では、共用体のメンバーは共用体の型自体で見つかります。 ただし、共用体の型にIUnionMembersというインターフェイスの宣言が直接含まれている場合、インターフェイスは共用体メンバー プロバイダーとして機能します。 その場合、共用体メンバーは共用体の型自体ではなく、共用体メンバー・プロバイダー でのみ 検出されます。
共用体メンバー・プロバイダー・インターフェースはパブリックでなければなりません。共用体の型自体は、それをインターフェースとして実装する必要があります。
共用体メンバーが見つかった 型には共用体定義型 という用語を使用します。共用体メンバー プロバイダーが存在する場合は共用体の型、それ以外の場合は共用体の型自体を使用します。
Union メンバー
共用体メンバーは、共用体定義型の名前と署名によって検索されます。 共用体定義型で直接宣言する必要はありませんが、継承できます。
共用体メンバーがパブリックでないことを示すエラーです。
作成メンバーと Value プロパティは必須であり、総称して 基本共用体パターンと呼ばれます。
HasValueメンバーとTryGetValue メンバーは、まとめて非ボックス化共用体アクセス パターンと呼ばれます。
次に、さまざまな共用体メンバーについて説明します。
共用体作成メンバー
共用体作成メンバーは、ケース型の値から新しい共用体値を作成するために使用されます。
共用体定義型が共用体型自体の場合、単一パラメーターを持つ各コンストラクターは 共用体コンストラクターです。 共用体のケース型は、次の方法でこれらのコンストラクターのパラメーター型から構築された型のセットとして識別されます。
- パラメーター型が null 許容型 (値か参照かに関係なく) の場合、ケース型は基になる型です
- それ以外の場合、ケース型はパラメーター型です。
// Union constructor making `Dog` a case type
public Pet(Dog value) { ... }
// Union constructor making `int` a case type
public Union(int? value) { ... }
// Union constructor making `string` a case type
public Union(string? value) { ... }
共用体定義型が共用体メンバー・プロバイダーの場合、単一パラメーターを持つ各静的 Create メソッドと、共用体の型自体に対して ID 変換可能な戻り値の型は 共用体ファクトリ メソッドです。
共用体のケース型は、次のように、これらのファクトリ メソッドのパラメーター型から構築された型のセットとして識別されます。
- パラメーター型が null 許容型 (値か参照かに関係なく) の場合、ケース型は基になる型です
- それ以外の場合、ケース型はパラメーター型です。
// Union factory method making `Cat` a case type
public static Pet Create(Cat value) { ... }
// Union factory method making `int` a case type
public static Union Create(int? value) { ... }
// Union factory method making `string` a case type
public static Union Create(string? value) { ... }
共用体コンストラクターと共用体ファクトリ メソッドは、共用 体作成メンバーと総称されます。
共用体作成メンバーの単一パラメーターは、値渡しパラメーターまたは in パラメーターである必要があります。
共用体型には、少なくとも 1 つの共用体作成メンバーが必要であり、したがって少なくとも 1 つのケース・タイプが必要です。
Value プロパティ
Value プロパティを使用すると、ケースの種類に関係なく、共用体に含まれる値にアクセスできます。
共用体を定義するすべての型は、object?型またはobject型のValueプロパティを宣言する必要があります。 プロパティには get アクセサーが必要であり、必要に応じて、任意のアクセシビリティの init または set アクセサーを持つ場合があり、コンパイラでは使用されません。
// Union 'Value' property
public object? Value { get; }
ボックス化されていないアクセス メンバー
共用体の種類は、ボックス 化されていない共用体アクセス パターンを追加で実装することを選択できます。これにより、各ケースの種類に対して厳密に型指定された条件付きアクセスと、null をチェックする方法が可能になります。
これにより、ケース型が値型であり、共用体内に格納されている場合に、コンパイラはパターン マッチングをより効率的に実装できます。
ボックス化されていないアクセス メンバーは次のとおりです。
- パブリック
getアクセサーを持つbool型のHasValueプロパティ。 必要に応じて、initアクセサーまたはsetアクセサーを持つことができます。これは、任意のアクセシビリティであり、コンパイラでは使用されません。 - ケースの種類ごとに
TryGetValueメソッド。 このメソッドはboolを返し、ケース型に ID 変換可能な型の単一の out パラメーターを受け取ります。
// Non-boxing access members
public bool HasValue { get { ... } }
public bool TryGetValue(out Dog value) { ... }
HasValue は、共用体の Value が null でない場合にのみ true を返します。
TryGetValue は、共用体の Value が指定されたケース型の場合にのみ true を返し、その場合はメソッドの out パラメーターでその値を渡します。
形式の一貫性
言語とコンパイラは、共用体の型に関して多くの動作を想定しています。 型が共用体型として修飾されていても、それらの前提を満たしていない場合、共用体の動作が期待どおりに機能しない可能性があります。
-
Soundness:
Valueプロパティは、常に null またはケース型の値に評価されます。 これは、共用体型の既定値の場合でも当てはまります。 -
安定性: 大文字と小文字の種類から共用体の値が作成された場合、
Valueプロパティはそのケースの型または null と一致します。 共用体値がnull値から作成された場合、Valueプロパティはnullされます。 - 作成の等価性: 1 つの値が 2 つの異なるケース型に暗黙的に変換できる場合、それらのケース型の作成メンバーは、その値で呼び出されたときに同じ監視可能な動作を持ちます。
-
アクセス パターンの整合性:
HasValueとTryGetValueボックス化されていないアクセス メンバーの動作 (存在する場合) は、Valueプロパティに対して直接チェックする場合と同じです。
共用体型の例
Pet では、共用体の型自体に基本的な共用体パターンが実装されます。
[Union] public record struct Pet
{
// Creation members = case types are 'Dog' and 'Cat'
public Pet(Dog value) => Value = value;
public Pet(Cat value) => Value = value;
// 'Value' property
public object? Value { get; }
}
IntOrBool では、共用体の型自体に非ボックス化アクセス パターンが実装されます。
public record struct IntOrBool
{
private bool _isBool;
private int _value;
public IntOrBool(int value) => (_isBool, _value) = (false, value);
public IntOrBool(bool value) => (_isBool, _value) = (true, value ? 1 : 0);
public object Value => _isBool ? _value is 1 : _value;
public bool HasValue => true;
public bool TryGetValue(out int value)
{
value = _value;
return !_isBool;
}
public bool TryGetValue(out bool value)
{
value = _isBool && _value is 1;
return _isBool;
}
}
メモ: これは、ボックス化されていないアクセス パターンを実装する方法の一例にすぎません。 ユーザー コードは、好きな方法でコンテンツを格納できます。 特に、実装がボックス化を妨げることはありません。 名前の non-boxing は、コンパイラのパターン マッチング実装が、 object?型の Value プロパティではなく、厳密に型指定された方法で各ケースの型にアクセスできるようにすることを意味します。
Result<T> では、共用体メンバー プロバイダーを介して基本パターンが実装されます。
public record class Result<T> : Result<T>.IUnionMembers
{
object? _value;
public interface IUnionMembers
{
public static Result<T> Create(T value) => new() { _value = value };
public static Result<T> Create(Exception value) => new() { _value = value };
public object? Value { get; }
}
object? IUnionMembers.Value => _value;
}
共用体の動作
共用体の動作は、通常、基本的な共用体パターンを使用して実装されます。 共用体が非ボックス化アクセス パターンを提供する場合、共用体パターンマッチングはそれを優先的に利用します。
共用体の変換
共用体変換は、それぞれのケース型から共用体型に暗黙的に変換します。 具体的には、Eから型Cへの標準的な暗黙的な変換があり、CがUの共用体作成メンバーのパラメーター型である場合、型または式Eから共用体型Uへの共用体変換があります。
共用体型Uが構造体の場合、Eから型Cへの標準的な暗黙的な変換があり、CがUの共用体作成メンバーのパラメーター型である場合、型または式Eから型U?への共用体変換があります。
共用体変換自体は、標準の暗黙的な変換ではありません。 したがって、ユーザー定義の暗黙的な変換や別の共用体変換には関与しない可能性があります。
暗黙的な共用体変換を超える明示的な共用体変換はありません。 したがって、 E から共用体のケース型 Cへの明示的な変換がある場合でも、 E からその共用体型への明示的な変換があることを意味するものではありません。
共用体変換は、共用体の作成メンバーを呼び出すことによって実行されます。
Pet pet = dog;
// becomes
Pet pet = new Pet(dog);
// and
Result<string> result = "Hello"
//becomes
Result<string> result = Result<string>.IUnionMembers.Create("Hello");
オーバーロード解決で最適な候補メンバーが 1 つ見つからない場合、またはそのメンバーが共用体型の共用体メンバーの 1 つでない場合はエラーです。
共用体変換は、暗黙的なユーザー定義変換のもう 1 つの "形式" です。 適用可能なユーザー定義変換演算子 "shadows" 共用体変換。
この決定の背後にある根拠は次のとおりです。
誰かがユーザー定義演算子を記述した場合は、優先度を取得する必要があります。 つまり、ユーザーが実際に独自の演算子を記述した場合は、それを呼び出す必要があります。 変換演算子が共用体型に変換された既存の型は、現在の演算子を使用する既存のコードに関して引き続き同じように動作します。
次の例では、暗黙的なユーザー定義変換が共用体変換よりも優先されます。
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => ...
public S1(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static implicit operator S1(int x) => ...
}
class Program
{
static S1 Test1() => 10; // implicit operator S1(int x) is used
static S1 Test2() => (S1)20; // implicit operator S1(int x) is used
}
次の例では、明示的なキャストをコードで使用すると、明示的なユーザー定義変換が共用体変換よりも優先されます。 ただし、コードに明示的なキャストがない場合は、明示的なユーザー定義変換が適用されないため、共用体変換が使用されます。
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => ...
public S2(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static explicit operator S2(int x) => ...
}
class Program
{
static S2 Test3() => 10; // Union conversion S2.S2(int) is used
static S2 Test4() => (S2)20; // explicit operator S2(int x)
}
和集合の照合
パターンの受信値が共用体型または共用体型の null 許容の場合、パターンによっては、null 許容値と基になる共用体値の内容が "ラップ解除" される可能性があります。
無条件の _ および var パターンの場合、パターンは受信値自体に適用されます。 例えば次が挙げられます。
if (GetPet() is var pet) { ... } // 'pet' is the union value returned from `GetPet`
ただし、他のすべてのパターンは、基になる共用体の Value プロパティに暗黙的に適用されます。
if (GetPet() is Dog dog) { ... } // 'Dog dog' is applied to 'GetPet().Value'
if (GetPet() is null) { ... } // 'null' is applied to 'GetPet().Value'
if (GetPet() is { } value) { ... } // '{ } value' is applied to 'GetPet().Value'
論理パターンの場合、このルールは分岐に個別に適用されます。これは、 and パターンの左分岐が右分岐の着信の種類に影響を与える可能性があることに留意してください。
GetPet() switch
{
var pet and not null => ... // 'var pet' applies to the incoming 'Pet' and 'not null' to its 'Value'
not null and var value => ... // 'not null' applies to the 'Value' as does 'var value' because of the
// left branch changing the incoming type to `object?`.
}
メモ:このルールは、Pet共用体自体ではなく、コンテンツにPetが適用されるため、GetPet() is Pet petが成功しない可能性があることを意味します。
メモ:無条件varパターンの異なる扱い(本質的にvar _の短縮形である_)の理由は、それらの使用が他のパターンと定性的に異なるという前提です。
var パターンは、一致する値に単に名前を付けるために使用されます。多くの場合、 PetOwner{ Pet: var pet }などの入れ子になったパターンで使用されます。 ここでは、役に立つセマンティクスは、Value プロパティが役に立たないobject?型に逆参照されるのではなく、共用体の型Petを保持petです。
受信値がクラス型の場合、共用体値自体がnullされているか、含まれる値がnullかに関係なく、null パターンは成功します。
if (result is null) { ... } // if (result == null || result.Value == null)
その他の共用体一致パターンは、共用体値自体が nullされていない場合にのみ成功します。
if (result is 1) { ... } // if (result != null && result.Value is 1)
同様に、受信値が null 許容値型 (構造体共用体型をラップする) の場合、受信値自体がnullされているか、含まれる値がnullかに関係なく、null パターンは成功します。
if (result is null) { ... } // if (result.HasValue == false || result.GetValueOrDefault().Value == null)
他の共用体一致パターンは、受信値自体が nullされていない場合にのみ成功します。
if (result is 1) { ... } // if (result.HasValue && result.GetValueOrDefault().Value is 1)
コンパイラは、非ボックス化アクセス パターンで規定されたメンバーを使用してパターン動作を実装することを好みます。 整形式ルールの範囲内で最適化を行うのは自由ですが、適用が保証される最小セットは次のとおりです。
- 特定の型
Tのチェックを意味するパターンの場合、TryGetValue(S value)メソッドが使用可能で、id、または暗黙的な参照/ボックス化変換がTからSの場合は、そのメソッドを使用して値を取得します。 その後、その値にパターンが適用されます。 このような方法が複数ある場合は、TからSへの変換が可能な場合は、ボックス化変換ではない場合に優先されます。 まだ複数のメソッドがある場合は、実装定義の方法で 1 つが選択されます。 - それ以外の場合、
nullのチェックを意味するパターンの場合、HasValueプロパティが使用可能な場合、そのプロパティは共用体の値が null かどうかを確認するために使用されます。 - それ以外の場合、パターンは、受信共用体の
IUnion.Valueプロパティにアクセスした結果に適用されます。
共用体型に適用される is-type 演算子は、共用体型に適用される型パターンと同じ意味を持ちます。
和集合の網羅性
共用体の型は、そのケース型によって "使い果たされる" と見なされます。 つまり、共用体のすべてのケース型を処理する場合、 switch 式は完全です。
var name = pet switch
{
Dog dog => ...,
Cat cat => ...,
// No warning about non-exhaustive switch
};
NULL 値の許容
共用体の Value プロパティの null 状態は、他のプロパティと同様に追跡され、次の変更が加えられます。
- 共用体作成メンバーが (明示的に、または共用体変換を介して) 呼び出されると、新しい共用体の
Valueは、受信値の null 状態を取得します。 - 非ボックス化アクセス パターンの
HasValueまたはTryGetValue(...)を使用して共用体の型の内容を照会する場合 (明示的に、またはパターン マッチングを使用して)、Valueが直接チェックされた場合と同じように、Valueの null 許容状態に影響します。Valueの null 状態は、trueブランチで "null ではない" になります。
共用体スイッチが完全な場合でも、受信共用体の Value プロパティの null 状態が "null" の場合、未処理の null に対して警告が表示されます。
Pet pet = GetNullableDog(); // 'pet.Value' is "maybe null"
var value = pet switch
{
Dog dog => ...,
Cat cat => ...,
// Warning: 'null' not handled
}
共用体インターフェイス
次のインターフェイスは、共用体機能の実装で言語によって使用されます。
共用体アクセス・インターフェース
IUnion インターフェイスは、コンパイル時に型を共用体型としてマークし、実行時に共用体の内容にアクセスする方法を提供します。
public interface IUnion
{
// The value of the union or null
object? Value { get; }
}
コンパイラによって生成された共用体は、このインターフェイスを実装します。
使用例:
if (value is IUnion { Value: null }) { ... }
共用体宣言
共用体宣言は、C# で共用体型を宣言する簡潔で意見の分かれている方法です。 これらは、 Valueを格納するために単一のオブジェクト参照を使用する構造体を宣言します。これは、次のことを意味します。
- ボックス化: ケース型の中の値型は、入力時にボックス化されます。
- コンパクト: 和集合値には 1 つのフィールドのみが含まれます。
意図は、大部分のユース ケースを非常にうまくカバーするための共用体宣言です。 共用体宣言を使用するのではなく、特定の共用体の型を手動でコーディングする主な 2 つの理由は次のとおりです。
- 共用体の動作を得るために、既存の型を共用体パターンに適応させる。
- 効率性や相互運用の理由など、別のストレージ戦略を実装する。
構文
共用体宣言には、名前と 共用体コンストラクター 型のリストがあります。
union_declaration
: attributes? struct_modifier* 'partial'? 'union' identifier type_parameter_list?
'(' type (',' type)* ')' struct_interfaces? type_parameter_constraints_clause*
(`{` struct_member_declaration* `}` | ';')
;
構造体メンバー (§16.3) の制限に加えて、共用体メンバーには次のことが適用されます。
- インスタンス フィールド、自動プロパティ、またはフィールドに似たイベントは許可されません。
- 1 つのパラメーターを使用して明示的に宣言されたパブリック コンストラクターは許可されません。
- 明示的に宣言されたコンストラクターは、生成されたコンストラクターのいずれかに対する (直接または間接的に) デリゲートに対して
this(...)初期化子を使用する必要があります。
共用体コンストラクターの型には、インターフェイス、型パラメーター、null 許容型、その他の共用体など、objectに変換する任意の型を指定できます。 結果として得られるケースが重なったり、共用体が入れ子になったり null になったりしても問題ありません。
例:
// Union of existing types
public union Pet(Cat, Dog, Bird);
// Union with function member
public union OneOrMore<T>(T, IEnumerable<T>)
{
public IEnumerable<T> AsEnumerable() => Value switch
{
IEnumerable<T> list => list,
T value => [value],
}
}
// "Discriminated" union with freshly declared case types
public record class None();
public record class Some<T>(T value);
public union Option<T>(None, Some<T>);
#### Lowering
A union declaration is lowered to a struct declaration with
* the same attributes, modifiers, name, type parameters and constraints,
* implicit implementations of `IUnion`,
* a `public object? Value { get; }` auto-property,
* a public constructor for each *union constructor* type,
* any members in the union declaration's body.
It is an error for user-declared members to conflict with generated members.
Example:
``` c#
public union Pet(Cat, Dog){ ... }
次の値に下げられます。
[Union] public struct Pet : IUnion
{
public Pet(Cat value) => Value = value;
public Pet(Dog value) => Value = value;
public object? Value { get; }
... // original body
}
質問を開く
[解決済み]共用体宣言はレコードですか?
共用体宣言がレコード構造体に下げる
私は、このデフォルトの動作は不要であり、構成可能ではないことを考えると、使用シナリオを大幅に制限すると思います。 レコードは、使用されていないか、特定の要件に一致しない多くのコードを生成します。 たとえば、コンパイラのコード ベースでは、そのコードが肥大化しているため、レコードはほとんど禁止されています。 私はデフォルトを変更する方が良いと思います:
- 既定では、共用体宣言は、共用体固有のメンバーのみを持つ通常の構造体を宣言します。
- ユーザーはレコード共用体を宣言できます。
record union U(E1, ...) ...
解像 度: 共用体宣言は、レコード構造体ではなく、プレーン構造体です。
record union ...はサポートされていません
[解決済み]共用体宣言の構文
提案された構文が不完全であるか、不必要に制限されているようです。 たとえば、base 句は許可されていないようです。 しかし、私は簡単に例えばインターフェイスを実装する必要性を想像することができます。
私は、要素型リストとは別に、構文はstructキーワードがunionキーワードに置き換えられる通常のstruct/record struct宣言と一致する必要があると思います。
解像 度: 制限は削除されます。
[解決済み]共用体宣言メンバー
インスタンス フィールド、自動プロパティ、またはフィールドに似たイベントは許可されません。
これは恣意的で絶対に不要だと感じます。
解像 度: 制限は保持されます。
[解決済み]和集合ケース型としての Null 許容値型
共用体のケース型は、これらのコンストラクターからのパラメーター型のセットとして識別されます。 共用体のケース型は、これらのファクトリ メソッドのパラメーター型のセットとして識別されます。
同時に:
ケースの種類ごとに
TryGetValueメソッド。 このメソッドはboolを返し、指定されたケース型に対応する型の 1 つの out パラメーターを次のように受け取ります。
- case 型が null 許容値型の場合、パラメーターの型は基になる型に ID 変換可能である必要があります
- それ以外の場合、型はケース型に ID 変換可能である必要があります。
特に型パターンがターゲット型として null 許容値型を使用できないという利点は、ケース型の中で null 許容値型を持つことですか? コンストラクター/ファクトリのパラメーター型が null 許容値型の場合、対応するケース型が基になる型であると単純に言うことができるように感じます。 その後、 TryGetValue メソッドに追加の句は必要ありません。すべての出力パラメーターはケース型です。
解像 度: 提案が承認されました
[解決済み]プロパティの既定の null 許容状態Value
いずれのケース型も null 許容でない共用体型の場合、
Valueの既定の状態は "null ではない" ではなく "null" になります。
新しい設計では、 Value プロパティは一部の一般的なインターフェイスでは定義されていませんが、宣言された型に特に属する API であるため、上記で引用した規則はオーバーエンジニアリングのように感じます。 さらに、この規則は、null 許容型が使用されない状況で、コンシューマーに null 許容型を強制的に使用させる可能性があります。
たとえば、次の共用体宣言を考えてみましょう。
union U1(int, bool, DateTime);
引用符で囲まれた規則によると、 Value の既定の状態は "not null" です。 ただし、これは型の動作と一致しません。 default(U1).Value は null。 動作を再調整するために、コンシューマーは少なくとも 1 つのケース型を null 許容にすることを強制されます。 次のようなものがあります。
union U1(int?, bool, DateTime);
しかし、これは望ましくない可能性があります。コンシューマーは、 int? 値を使用した明示的な作成を許可したくない場合があります。
提案: 引用符で囲まれたルールを削除します。null 許容分析では、 Value プロパティの注釈を使用して、既定の null 許容を推測する必要があります。
解像 度: 提案が承認される
[解決済み]和集合値型の Null 許容の和集合一致
パターンの受信値が共用体型の場合、パターンによっては、共用体値の内容が "ラップ解除" される場合があります。
パターンの受信値が Nullable<union type>の場合、このルールをシナリオに拡張する必要がありますか?
次のシナリオを考えてみましょう。
static bool Test1(StructUnion? u)
{
return u is 1;
}
static bool Test2(ClassUnion? u)
{
return u is 1;
}
Test1 と Test2 の u is 1 の意味は大きく異なります。 Test1 では和集合一致ではなく、Test2 では和集合が一致します。
パターン マッチングは通常、他の状況で行うので、"共用体マッチング" は Nullable<T> を "掘り下げる" 必要があります。
それを行う場合、Nullable<union type>に対するnullパターンを照合する和集合は、クラスに対して同様に機能する必要があります。
つまり、パターンは (!nullableValue.HasValue || nullableValue.Value.Value is null)場合は true です。
解像 度: 提案が承認されます。
"bad" API について何を行う必要がありますか?
コンパイラは、一致しているように見えるが、それ以外の場合は "bad" である共用体一致 API について何を行う必要がありますか? たとえば、コンパイラはシグネチャが一致する TryGetValue/HasValue を検索しますが、必要なカスタム修飾子や不明な機能などが必要なため、"不適切" です。コンパイラは API を自動的に無視するか、エラーを報告する必要がありますか? 同様に、API は廃止/試験的としてマークされている可能性があります。 コンパイラが診断を報告するか、API をサイレントモードで使用するか、API を使用しないか。
共用体宣言の型が見つからない場合
UnionAttribute、IUnion、またはIUnion<TUnion>が見つからない場合はどうなりますか? エラー。 合成。 他に何か?
[解決済み]汎用 IUnion インターフェイスの設計
IUnion<TUnion>がIUnionから継承したり、型パラメーターをIUnion<TUnion>に制限したりしてはならない引数が作成されました。 もう一度見直す必要があります。
解像 度: 現在、 IUnion<TUnion> インターフェイスは削除されています。
[解決済み]ケース型としての Null 許容値型とその対話 TryGetValue
上記の規則では、ケース型が null 許容値型の場合、対応する TryGetValue メソッドで使用されるパラメーター型は 基になる 型である必要があります。
これは、このメソッドによって null 値が生成されないという事実によって動機付けられます。 消費側では、null 許容値型は型パターンとして許可されませんが、基になる型に対する一致は、このメソッドの呼び出しにマップできる必要があります。
このラップ解除に同意することを確認する必要があります。
解像 度: 同意/確認済み
ボックス化されていない共用体アクセス パターン
適切な HasValue と TryGetValue API を見つけるための正確な規則を指定する必要があります。
継承は関係していますか? 読み取り/書き込み HasValue 許容される一致ですか? その他
[解決済み] TryGetValue 一致する変換
[Union Matching]\(和集合の照合\) セクションには次のように表示されます。
特定の型
Tのチェックを意味するパターンの場合、TryGetValue(S value)メソッドが使用可能で、TからSへの暗黙的な変換がある場合は、そのメソッドを使用して値を取得します。
暗黙的な変換のセットは何らかの方法で制限されますか? たとえば、ユーザー定義の変換は許可されますか? タプル変換やその他のそれほど簡単ではない変換はどうですか? 一部は標準の変換です。
TryGetValueメソッドのセットは他の方法で制限されていますか? たとえば、Union Patterns セクションは、ケース型に一致するパラメーター型を持つメソッドのみが考慮されることを意味します。
各ケースの種類
Tのpublic bool TryGetValue(out T value)メソッド。
明示的な答えを持つことは良いでしょう。
解像 度: 暗黙的な ID、参照、またはボックス化の変換のみが考慮されます
TryGetValue および null 許容分析
非ボックス化アクセス パターンの
HasValueまたはTryGetValue(...)を使用して共用体の型の内容を照会する場合 (明示的に、またはパターン マッチングを使用して)、Valueが直接チェックされた場合と同じように、Valueの null 許容状態に影響します。Valueの null 状態は、trueブランチで "null ではない" になります。
TryGetValueメソッドのセットは何らかの方法で制限されていますか? たとえば、Union Patterns セクションは、ケース型に一致するパラメーター型を持つメソッドのみが考慮されることを意味します。
各ケースの種類
Tのpublic bool TryGetValue(out T value)メソッド。
明示的な答えを持つことは良いでしょう。
構造体共用体型の default 値に関する規則を明確にする
注: 以下に示す既定の null 許容ルールは削除されています。
注: 以下に示す "既定" の整形式ルールは削除されています。 これが必要であることを確認する必要があります。
Nullability セクションには次のように表示されます。
いずれのケース型も null 許容でない共用体型の場合、
Valueの既定の状態は "null ではない" ではなく "null" になります。
次の例では、現在の実装では、s2のValueが "not null" と見なされます。
S2 s2 = default;
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => throw null!;
public S2(bool x) => throw null!;
object? System.Runtime.CompilerServices.IUnion.Value => throw null!;
}
同時に、 整形式 セクションには次のように表示されます。
- 既定値: 共用体型が値型の場合、既定値は
Valueとしてnull。- 既定のコンストラクター: 共用体の型に nullary (引数なし) コンストラクターがある場合、結果の共用体は
Valueとしてnull。
このような実装は、上記の例の null 許容分析動作と矛盾します。
整形式ルールを調整するか、defaultのValueの状態を "null" にする必要がありますか?
後者の場合、初期化 S2 s2 = default; null 許容警告を生成する必要がありますか?
1 つに制約されている場合でも、型パラメーターが共用体型でないことを確認します。
class C1 : System.Runtime.CompilerServices.IUnion
{
private readonly object _value;
public C1(int x) { _value = x; }
public C1(string x) { _value = x; }
object System.Runtime.CompilerServices.IUnion.Value => _value;
}
class Program
{
static bool Test1<T>(T u) where T : C1
{
return u is int; // Not a union matching
}
static bool Test2<T>(T u) where T : C1
{
return u is string; // Not a union matching
}
}
条件後属性は、Union インスタンスの既定の null 許容に影響する必要がありますか?
注: 以下に示す既定の null 許容ルールは削除されています。 また、共用体の作成方法から、 Value プロパティの既定の null 許容を推測しなくなりました。 したがって、質問は現在の設計に適用されなくなりました。
いずれのケース型も null 許容でない共用体型の場合、
Valueの既定の状態は "null ではない" ではなく "null" になります。
次のシナリオで警告が予想される場合
#nullable enable
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null!;
public S1([System.Diagnostics.CodeAnalysis.NotNull] bool? x) => throw null!;
object? System.Runtime.CompilerServices.IUnion.Value => throw null!;
}
class Program
{
static void Test2(S1 s)
{
// warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
// For example, the pattern 'null' is not covered.
_ = s switch { int => 1, bool => 3 }; //
}
}
共用体の変換
[解決済み]彼らは他の変換の中で優先順位のどこに属していますか?
和集合変換は、ユーザー定義変換の別の形式のように感じます。 したがって、現在の実装では、暗黙的なユーザー定義変換を分類しようとして失敗した直後にそれらを分類し、存在する場合はユーザー定義変換の別の形式として扱われます。 これには、次のような結果が生まれます。
- 暗黙的なユーザー定義変換は、共用体変換よりも優先されます
- 明示的なキャストがコードで使用されている場合、明示的なユーザー定義変換は共用体変換よりも優先されます
- コードに明示的なキャストがない場合、共用体変換は明示的なユーザー定義変換よりも優先されます
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => ...
public S1(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static implicit operator S1(int x) => ...
}
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(int x) => ...
public S2(string x) => ...
object System.Runtime.CompilerServices.IUnion.Value => ...
public static explicit operator S2(int x) => ...
}
class Program
{
static S1 Test1() => 10; // implicit operator S1(int x) is used
static S1 Test2() => (S1)20; // implicit operator S1(int x) is used
static S2 Test3() => 10; // Union conversion S2.S2(int) is used
static S2 Test4() => (S2)20; // explicit operator S2(int x)
}
これが好きな動作であることを確認する必要があります。 それ以外の場合は、変換規則を明確にする必要があります。
解決策:
作業グループによって承認されます。
[解決済み]コンストラクターのパラメーターの ref-ness
現在、言語では、ユーザー定義の変換演算子に対して値によるパラメーターと in パラメーターのみを使用できます。
この制限の理由は、共用体変換に適したコンストラクターにも当てはまるように感じます。
提案:
上記のセクションで case type constructor の定義 Union types 調整します。
-For each public constructor with exactly one parameter, the type of that parameter is considered a *case type* of the union type.
+For each public constructor with exactly one **by-value or `in`** parameter, the type of that parameter is considered a *case type* of the union type.
解決策:
今のところ、作業グループによって承認されています。 ただし、ケース型コンストラクターのセットと、共用体型変換に適したコンストラクターのセットを "分割" することを検討できます。
[解決済み]Null 許容変換
Null 許容変換 セクションには、基になる変換として使用できる変換が明示的に一覧表示されます。 現在の仕様では、そのリストに対する調整は提案されていません。 これにより、次のシナリオでエラーが発生します。
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1? Test1(int x)
{
return x; // error CS0029: Cannot implicitly convert type 'int' to 'S1?'
}
}
提案:
和集合変換によってサポートされる S から T? への暗黙的な null 許容変換をサポートするように仕様を調整します。
具体的には、Tが共用体型であると仮定すると、Eから型Cへの和集合変換があり、Cがケース型のTである場合、型または式Eから型T?への暗黙的な変換があります。
Eの型を null 非許容値型にする必要はありません。
変換は、 S から T への基になる共用体変換として評価され、その後に T から への折り返しが続きます。 T?
解決策:
承認。
[解決済み]リフトされた変換
リフトされた和集合変換をサポートするように リフト変換 セクションを調整しますか? 現在、これらは許可されていません。
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(int x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(int? x)
{
return x; // error CS0029: Cannot implicitly convert type 'int?' to 'S1'
}
static S1? Test2(int? y)
{
return y; // error CS0029: Cannot implicitly convert type 'int?' to 'S1?'
}
}
解決策:
現時点では、リフトされた共用体変換はありません。 ディスカッションの一部の注意事項:
ユーザー定義の変換に例えると、ここで少し分かっています。 一般に、共用体には、入ってくる null 値を含めることができません。 リフティングで、
null値が格納された共用体型のインスタンスを作成する必要があるかどうか、またはNullable<Union>のnull値を作成する必要があるかどうかは明らかではありません。
[解決済み]基本型のインスタンスからの共用体変換をブロックしますか?
現在の動作がわかりにくい場合があります。
struct S1 : System.Runtime.CompilerServices.IUnion
{
public S1(System.ValueType x)
{
}
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(System.ValueType x)
{
return x; // Union conversion
}
static S1 Test2(System.ValueType y)
{
return (S1)y; // Unboxing conversion
}
}
言語では、基本型からのユーザー定義変換の宣言を明示的に禁止することに注意してください。 したがって、そのような共用体変換を許可しないようにする場合があります。
解決策:
今のところ特別なことは何もしないでください。 一般的なシナリオを完全に保護することはできません。
[解決済み]インターフェイス型のインスタンスからの共用体変換をブロックしますか?
現在の動作がわかりにくい場合があります。
struct S1 : I1, System.Runtime.CompilerServices.IUnion
{
public S1(I1 x) => throw null;
public S1(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
interface I1 { }
struct S2 : System.Runtime.CompilerServices.IUnion
{
public S2(I1 x) => throw null;
public S2(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class C3 : System.Runtime.CompilerServices.IUnion
{
public C3(I1 x) => throw null;
public C3(string x) => throw null;
object System.Runtime.CompilerServices.IUnion.Value => throw null;
}
class Program
{
static S1 Test1(I1 x)
{
return x; // Union conversion
}
static S1 Test2(I1 x)
{
return (S1)x; // Unboxing
}
static S2 Test3(I1 x)
{
return x; // Union conversion
}
static S2 Test4(I1 x)
{
return (S2)x; // Union conversion
}
static C3 Test3(I1 x)
{
return x; // Union conversion
}
static C3 Test4(I1 x)
{
return (C3)x; // Reference conversion
}
}
言語では、基本型からのユーザー定義変換の宣言を明示的に禁止することに注意してください。 したがって、そのような共用体変換を許可しないようにする場合があります。
解決策:
今のところ特別なことは何もしないでください。 一般的なシナリオを完全に保護することはできません。
IUnion インターフェイスの名前空間
IUnion インターフェイスの名前空間を含む場合は、未指定のままです。
global名前空間に保持することが意図されている場合は、明示的に指定しましょう。
提案:これが単に見落とされている場合は、 System.Runtime.CompilerServices 名前空間を使用できます。
Union型としてのクラス
[解決済み]インスタンス自体のチェック null
共用体型がクラス型の場合、その値自体が null である可能性があります。 では、null チェックはどうでしょうか。
nullパターンは、Valueプロパティをチェックするために共同選択されているため、共用体自体が null ではないことをどのように確認しますか?
例えば次が挙げられます。
-
SがUnion構造体の場合、s自体がnullされている場合S?値のs is nullはtrueです。CがUnionクラスの場合、C?の値のc is nullはfalsec自体がnullされるときですが、c自体がnullされず、c.Valueがnullされている場合はtrueされます。
別の例を示します。
class C1 : IUnion
{
private readonly object? _value;
public C1(){}
public C1(int x) { _value = x; }
public C1(string x) { _value = x; }
object? IUnion.Value => _value;
}
class Program
{
static int Test1(C1? u)
{
// warning CS8655: The switch expression does not handle some null inputs (it is not exhaustive).
// For example, the pattern 'null' is not covered.
// This is very confusing, the switch expression is indeed not exhaustive (u itself is not
// checked for null), but there is a case 'null => 3' in the switch expression.
// It looks like the only way to shut off the warning is to use 'case _'. Adding it removes
// all benefits of exhaustiveness checking, any union case could be missing and there would
// be no diagnostic about that.
return u switch { int => 1, string => 2, null => 3 };
}
}
設計のこの部分は、共用体型が構造体であるという期待に沿って明確に最適化されています。 いくつかのオプション:
- ひどすぎる。 パターン一致の代わりに、null チェックに
==を使用します。 - 共用体の値とその
Valueプロパティ (u is null ==> u == null || u.Value == null) の両方に、nullパターン (および他のパターンでの暗黙的な null チェック) を適用します。 - クラスが共用体型になることを禁止します。
[解決済み] Union クラスからの派生
クラスが基底クラスとして Unionクラスを使用する場合、現在の仕様に従って、 Unionクラス自体になります。 これは、 IUnion インターフェイスの実装を自動的に "継承" するため、再実装する必要がないために発生します。 同時に、派生型のコンストラクターは、この新しい Union内の型のセットを定義します。 2 つのクラスの周りの非常に奇妙な言語の動作に到達するのは非常に簡単です。
class C1 : IUnion
{
private readonly object _value;
public C1(long x) { _value = x; }
public C1(string x) { _value = x; }
object IUnion.Value => _value;
}
class C2(int x) : C1(x);
class Program
{
static int Test1(C1 u)
{
// Good
return u switch { long => 1, string => 2, null => 3 };
}
static int Test2(C2 u)
{
// error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'long'.
// error CS8121: An expression of type 'C2' cannot be handled by a pattern of type 'string'.
return u switch { long => 1, string => 2, null => 3 };
}
}
いくつかのオプション:
クラス型が
Union型の場合に変更します。 たとえば、すべての true の場合、クラスはUnion型です。- 派生型は
Union型と見なされないため、sealedされ、混乱が生じます。 - そのベースのいずれも実装されていません
IUnion
これはまだ完璧ではありません。 ルールは微妙すぎます。 間違いを犯しやすいです。 宣言に診断はありませんが、
Union照合は機能しません。- 派生型は
クラスを共用体型にできないようにします。
[解決済み]is-type 演算子
is-type 演算子 は、ランタイム型チェックとして指定されます。 構文的には、型パターンのように見えますが、そうではありません。 そのため、特殊な Unionマッチは使用されないため、ユーザーの混乱につながる可能性があります。
struct S1 : IUnion
{
private readonly object _value;
public S1(int x) { _value = x; }
public S1(string x) { _value = x; }
object IUnion.Value => _value;
}
class Program
{
static bool Test1(S1 u)
{
return u is int; // warning CS0184: The given expression is never of the provided ('int') type
}
static bool Test2(S1 u)
{
return u is string and ['1', .., '2']; // Good
}
}
再帰共用体の場合、型パターンは警告を表示しない可能性がありますが、ユーザーが考える操作は実行されません。
解像 度: 型パターンとして機能する必要があります。
リストのパターン
リスト パターンは常に Union 一致で失敗します。
struct S1 : IUnion
{
private readonly object _value;
public S1(int[] x) { _value = x; }
public S1(string[] x) { _value = x; }
object IUnion.Value => _value;
}
class Program
{
static bool Test1(S1 u)
{
// error CS8985: List patterns may not be used for a value of type 'object'. No suitable 'Length' or 'Count' property was found.
// error CS0021: Cannot apply indexing with [] to an expression of type 'object'
return u is [10];
}
}
static class Extensions
{
extension(object o)
{
public int Length => 0;
}
}
その他の質問
- 共用体変換でのコンストラクターの使用と、共用体パターン マッチングでの
TryGetValue(...)の使用の両方は、複数のコンストラクターが適用される場合に、1 つを選択するだけで済みます。 これは整形式の規則に従って問題にならないはずですが、私たちはそれに慣れているのでしょうか? - この仕様は、共用体の型自体で見つかった
Valueプロパティではなく、IUnion.Valueプロパティの実装に微妙に依存しています。 これは、既存の型 (他の用途で独自のValueプロパティを持つ可能性があります) がパターンを実装するための柔軟性を高めるために使用されます。 しかし、これはぎこちなく、共用体型で他のメンバーが見つかり、直接使用される方法と矛盾しています。 変更を加える必要がありますか? その他のオプション:- パブリック
Valueプロパティを公開するには、共用体の型が必要です。 - パブリック
Valueプロパティが存在する場合は優先しますが、存在しない場合はIUnion.Valueの実装にフォールバックします (GetEnumerator規則と同様)。
- パブリック
- 提案された共用体宣言構文は、特にケースの型を表現する場合に、広く愛されていません。 これまでの代替案も批判に会いますが、最終的に変更を加える可能性があります。 現在の懸念事項の一部を次に示します。
- 大文字と小文字の種類の間の区切り記号としてのコンマは、その順序が重要であることを意味しているように見える場合があります。
- かっこで示されたリストは、(パラメーター名がないにもかかわらず) プライマリ コンストラクターのように見えすぎます。
- 列挙型とは異なり、中かっこに "ケース" があります。
- 共用体宣言では 1 つの参照フィールドを持つ構造体が生成されますが、同時実行コンテキストで使用する場合、予期しない動作の影響を受けやすくなります。 たとえば、ユーザー定義関数メンバーの逆参照が複数回
this場合、2 つのアクセスの間に別のスレッドによって、含まれる変数が全体として再割り当てされている可能性があります。 コンパイラは、必要に応じてthisをローカルにコピーするコードを生成できます。 それは必要ですか? 一般に、どの程度のコンカレンシー回復性が望ましく、合理的に達成可能ですか?
C# feature specifications