もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
あるとき、ジェネリクスで、
T
型の戻り値をもつメソッドを引数にとって、- 引数で受けたメソッドを実行する。
- 実行したときに例外なく実行出来たら、そのまま引数で受けたメソッドが返した値を返す
- もし例外が起きたら、nullを返す
というメソッドを作ろうとした。
具体的には、下記のコードの中のMyMethod1()
がそれ。
namespace ConsoleApp2 { internal class Program { enum MyEnum { Value1, Value2, Value3, } static void Main(string[] args) { MyEnum enm = MyEnum.Value1; var a = MyMethod1(() => { return enm; }); } private static T? MyMethod1<T>(Func<T> action) { try { return action.Invoke(); } catch (Exception ex) { return null;// ←★ここでエラーが出た } } } }
そうすると、下記のようなエラーが出た。
エラーの中では、'default(T)'を使うように言っているが、
上の例で'MyMethod1()'が呼ばれた場合だと、Tは'MyEnum'になるので、'default(T)'は、MyEnum.Value1
になって、nullにはなってくれない。
どうにかしてnullを返す方法はあるか?調べる。
解決方法
「型パラメーターの制約条件」を使う。
具体的には、'MyMethod1()'を下記のように変える。
private static T? MyMethod1<T>(Func<T> action) where T : struct // ←★コレを付ける! { try { return action.Invoke(); } catch (Exception ex) { return null; } }
'where T : struct'を付けると、'T'が値型であることになり、エラーが解消される。
なんでエラーが解消されるのか?
以下、私の理解。
値型を、上のMyMethod1()
に渡すという前提で、NGなパターン、OKなパターンについて考えてみた。
NGパターン①:戻り値をT
にして(戻り値に?
はナシ)、制約をつけていない場合
private static T MyMethod1<T>(Func<T> action)
というパターンについて、
制約(where)を付けていないと、MyMethod1<T>()
を使う側で、Tには
- null許容の値型
- null非許容の値型
のどちらも入れることができる。 →参照
null許容の値型がTに入ってきた場合は、nullを返せるのでreturn null
はおかしくないが、
null非許容の場合は、Tにはnullを入れられないので、上に挙げたエラーになる。
NGパターン②:戻り値をT?
にして、制約をつけていない場合
private static T? MyMethod1<T>(Func<T> action)
というパターンについても、パターン①と同じエラーが出る。(Null非許容の値型である可能性があるため・・・)
これが、ずっとなんでエラーになるのかがわからなかったのだが、こちらのページの、
https://ufcpp.net/study/csharp/sp2_nullable.html
「null許容型にできるのは、null許容型を除く値型のみ」「"多重にnull許容"はできない」というのを見て、なるほどと思った。
つまり、T
にnull許容の値型(例えばint?
とか)を入れてしまうと、T?
はint??
的な扱い(そんなことは書けないが)になるが、それはC#では不可なので、エラーになる。
と、理解した。(あってるかな...)
OKパターン:戻り値をT?
にして、struct(値型)制約をつける場合
「解決方法」のところに書いたやつ。これで、思ったように動く。
private static T? MyMethod1<T>(Func<T> action) where T : struct
T
にstruct制約を付けると、T
は確定で「null非許容の値型」になる。
なので、NGパターン②で挙げたような多重null許容になることはなく、「null許容の値型」を返すことができる。と理解。
これで、今回やりたいことはできた。
思ったこと
「null許容」というものができた経緯は、いろいろ歴史があったようで、そのためか理解するのも個人的に結構難しい。まだ理解しきれてない感じがする。
少しずつ勉強しようと思う。
「型パラメータ制約」のまとめ
下記のMS公式に、制約の種類と意味が載っている。
where T : struct
は、「構造体」縛りではなくて「値型」で、
where T : class
は、「クラス」縛りではなくて「参照型」の様子。
うまく使えば、ジェネリクスで作ったメソッドなどの使用範囲を適切に絞って、変な使い方されてバグる、みたいなのを減らせそう。
参考
C#8.0世代の総称型制約 ジェネリクスとnull許容性の関係
https://qiita.com/muniel/items/daa819c64ca66f152a39
MS公式 型パラメーターの制約 (C# プログラミング ガイド)
値型と参照型
https://ufcpp.net/study/csharp/oo_reference.html
C# でジェネリック メソッドから NULL を返すにはどうすればよいですか?
https://stackoverflow.com/questions/302096/how-can-i-return-null-from-a-generic-method-in-c