C#8.0時代の総称型制約
はじめに
C#8.0でnull許容性が追加されたことで、総称型制約にも機能が追加されました。
動作を検証している中で、既存のキーワードの動作の認識が間違っていた箇所があったので、復習の意味も含めて記事を書いてみます。
キーワード
C#8.0で新しいキーワードnotnull
が追加されました。null不許容の参照型または値型を示すことができます。
また、従来class
制約だったものが、class
,class?
の両制約に役割が分かれることになります。
ここで「おや?」と思ったのが、この記事を書いたキッカケです。何かというと「struct?
という制約ってあったっけ?」という疑問でした。結論から言えばこの制約はありません。
null許容性の話においては、参照型と値型では全く違うということをきちんと認識しておくことが必要です。
参照型 | 参照型? | unmanaged型 | 値型 | 値型? | |
---|---|---|---|---|---|
未指定 | 〇 | 〇 | 〇 | 〇 | 〇 |
notnull |
〇 | - | 〇 | 〇 | - |
class |
〇 | - | - | - | - |
class? |
〇 | 〇 | - | - | - |
struct |
- | - | 〇 | 〇 | - |
unmanaged |
- | - | 〇 | - | - |
new() |
☆ | ☆ | 〇 | 〇 | 〇 |
notnull,new() |
☆ | - | 〇 | 〇 | - |
class ,new() |
☆ | - | - | - | - |
class?,new() |
☆ | ☆ | - | - | - |
総称型でnull許容性を指定する方法
値型の場合
値型で、かつnull許容も不許容も受け入れる場合を考えてみます。
この場合、struct
制約を利用して、型引数をnull不許容に指定した上で、null許容したい箇所だけ?
(Nullable<T>
)を指定すれば良いのです。
null許容値型とnull不許容値型は違う型として認識されるので、必要な場合はオーバーロードを書くことができます。
// nullを許容しない
void Do<T>(T item)
where T : struct { }
// 引数でnullを許容する
void Do<T>(T? item)
where T : struct { }
参照型の場合
参照型の場合はというと、型引数でclass?
制約を利用する方法と、型引数ではclass
制約を使用したうえでnull許容したい箇所だけ?
を指定する方法の二通りが考えられます。
また、参照型ではnull許容性が違っても同じ型として認識されますので、オーバーロードとして書き分けることはできません。
// 型引数も引数もnull不許容参照型
void Do0<T>(T item)
where T : class { }
// 型引数はnull不許容参照型で、引数はnull許容参照型
void Do1<T>(T? item)
where T : class { }
// 型引数がnull許容参照型ならば、引数もnull許容参照型
// 型引数がnull不許容参照型ならば、引数もnull不許容参照型
void Do2<T>(T item)
where T : class? { }
// これは書けない
// void Do3<T>(T? item)
// where T : class? { }
補足:notnull
制約
notnull
制約の時、T?
型を指定することはできません。これは値型と参照型でnull許容性の扱いが全く異なるからです。
void Do<T>(T item)
where T : notnull { }
// これは書けない
//void Do<T>(T? item)
// where T : notnull { }
特殊な制約
使用頻度は高くないですが、特殊な制約についても調べてみます。
デリゲート型制約
デリゲート型制約もnull許容性を指定できるようになっています。null許容性に応じて、Delegate
,Delegate?
を指定できます。挙動も直感的で、例えばAction?
を受け入れるか受け入れないかが変わります。
Delegate |
Delegate? |
Action |
Action? |
|
---|---|---|---|---|
Delegate |
〇 | - | 〇 | - |
Delegate? |
〇 | 〇 | 〇 | 〇 |
列挙型制約
列挙型制約もnull許容性を指定できるようになっています。null許容性に応じて、Enum
,Enum?
を指定できます。ただし、Enum?
については、あまり直感的な挙動ではないことを覚えておきましょう。
MyEnum
という列挙型を定義したとして、Enum?
型制約のついた型引数にMyEnum?
を指定することはできません。これはstruct
制約と同じ理屈です。
総合的に考えると、Enum
制約はstruct
制約と組み合わせて使ったほうが扱いやすいように思います。
public abstract class Enum : ValueType {}
// 独自定義したとする
enum MyEnum { ... }
Enum |
Enum? |
MyEnum |
MyEnum? |
|
---|---|---|---|---|
Enum |
〇 | - | 〇 | - |
Enum? |
〇 | 〇 | 〇 | - |
struct Enum |
- | - | 〇 | - |
その他の仕様変更
C#8.0からunmanaged
制約の挙動が変わっています。これは入れ子のunmanaged型もunmanaged型と扱われるように改善されたためです。
例えばunmanaged
制約した型引数に対して、C#8.0からは(int,int)
を扱うことができるようになっています。
まとめ
C# 8.0 の目玉機能であるnull許容性の指定は総称型の利便性にも大きく貢献します。理解を深めてより利便性を享受したいと思います。