LoginSignup
6

More than 3 years have passed since last update.

C#8.0世代の総称型制約 ジェネリクスとnull許容性の関係

Last updated at Posted at 2019-10-01

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許容性の扱いが全く異なるからです。

notnull制約
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許容性の指定は総称型の利便性にも大きく貢献します。理解を深めてより利便性を享受したいと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
6