今回はC#に関する話題を一つ提供しよう。
最近まで知らなかったのだが、C#にはヌルに関してこんな便利な機能がある。
int? | Null許容型 |
?. | Null条件演算子 |
?? | Null合体演算子 |
ヌル三兄弟と呼んでも良いかも知れない。
当記事ではこれらのヌル三兄弟の使い方を紹介したい。
では本題に入ろう。
ヌル三兄弟の使い方
例えば文字列型のリストを要素二個の初期値で作成する。
そのリストの要素数をCountで求める。
C#ではこんな風に書ける。
List<string> arr = new List<string>() { "data1", "data2" }; int len = arr.Count; // 2になる
ごく普通の処理である。
しかしながら、一般の状況では何かのリストの要素数をCountプロパティで参照した場合に、もしそのリストの要素が未設定でリストがnullのままの状態である可能性もある。
そう言う場合にCountプロパティを参照すると
「オブジェクト参照がオブジェクトインスタンスに設定されていません。」
と言うエラーメッセージが出る。
例えば以下のサンプルコードを実行するとそのエラーが出る。
List<string> arr = null; // "オブジェクト参照がオブジェクトインスタンスに設定されていません。" 例外が出る int len = arr.Count;
要するに、arrがnullのままなのだが、nullに対して.Countメソッドを実行すると例外が出るのだ。
上記の例外発生を防止する為に一般的に行われる手法は、参照する予定の変数を事前にif文でnullチェックする手法だ。
それを次に示す。
if文を使う例外対策の一般的なやり方
まあ、誰でも良くやる手法であるが、事前にifチェックをする。
List<string> arr = null; if (arr != null) { int len = arr.Count; }
このようにif文を入れて事前にnullチェックをしておけば、例外が出る心配は無い。
ただしこの手法の欠点はプログラム中にこのような多数のif判定が入るとコードが煩雑になり見づらいのだ。
そこでヌル三兄弟の登場だ。
ヌル三兄弟とだんご3兄弟の関連性は分からない。
ヌル許容型とヌル条件演算子
上記のif判定によるnullチェックを簡単に一行で書く方法がこれだ!
List<string> arr = null; int? len = arr?.Count;
いきなりクエスチョンマーク?が二個も出てきたぞ‼
もう訳分からんと言う人もいるかもしれない。
実は、昔のワテも訳分からんかった。
まず、
int? len
の部分の ? の意味であるが、通常の int型にクエスチョンマークを付けるとヌル許容型の変数宣言となる。
つまり len と言う変数には整数だけでなく null と言う値も代入出来るようになる。
ちなみに string型は元々 null許容型なのでそのままでもnullを代入出来る。
一方、右辺の
= arr?.Count
の部分であるが、変数 arr がもし null なら ?. 以降の処理はせずに null を返す(つまりCountを参照しないので例外が出ない)。変数 arr が null でなければ通常通り Count を参照する。
この ?. がNull条件演算子だ。
上記コードを実行すると arr?. が評価されて null なのでその null が右辺の len に代入される。
これで例外は出ないように出来た。
でも、len は int型のままにしておきたいと言う人もいるだろう。ワテもそうだ。
int型は int型のまま整数のみを保持する目的で使いたい。int? なんて言うヘンテコなヌル許容型の整数変数にはしたくないのが人情だ。
そう言う場合にお勧めなのがヌル三兄弟の三番目のヌル合体演算子だ。
Null合体演算子 ??
上記のコードに Null合体演算子を追加したのが次の例だ。
// len は 0 になる List<string> arr = null; int len = arr?.Count ?? 0; // ?.はNull条件演算子 ??はNull合体演算子
変数lenはint型に戻った!
一方、右辺の末尾に
?? 0
と言うのが追加された。
この二個のクエスチョンマークの意味であるが、これは ?? のように二個まとめて使って一つの演算子( Null合体演算子)になるのだが、?? の左側の値が null で無いならその値を返す。もし ?? の左側の値が null ならば ?? の右側の値を返すと言う動作となる。
その結果、上記のコードを実行すると、まず arr は null なので ?. で評価されて null になり、次にその null が ?? で評価されると ?? の右側の 0 が得られる。その 0 の値が左辺の len に代入されると言う訳だ。
ちょっとややこしい。
まあ、このヌル合体演算子 ?? は必ずしもヌル条件演算子 ?. と一緒に使う必要は無いので単独で使っても良い。
でもまあこの例の様にヌル三兄弟で使うとC#のコードがとっても簡単に書けるので便利だ。
Null条件演算子 ?. を使う上での注意事項
例えばこんな条件判定が有ったとする。
if (a.b.c == null) { Console.WriteLine("①a.b.c == null"); }
具体的には、こんなふうに階層になっているクラスの場合などだ。
public class A { public B b; public A() { b = new B(); } public class B { public string c = null; public string c2 = "非null"; } }
こんなクラスに対して、
var a = new A(); // a.b = null; // nullにして実験してみると良い
を作成して実験してみると良い。
この場合、a!=null かつ a.b!=null で有れば a.b.c を参照しても問題無いのでNull参照エラーが出る事は無くif文の{}カッコの中が実行出来る。
でも、上で説明したようにa==null または a.b==null なら、a.b の参照あるいは a.b.cの参照を実行した時点でNull参照エラーが出る。
それを回避するには、
if (a?.b?.c == null) // System.NullReferenceException エラー対策した { Console.WriteLine("③a.b.c == null"); }
とすれば良い。
ところがここに落とし穴があるのだ。
それは何かと言うと、if (a?.b?.c == null) の判定だと
a=null あるいは a.b=null の場合にも true になってしまうのだ。
その結果 { }内の処理が実行される。
まあそれでも良い場合もある。
でも、例えばプログラマーが
の場合に限り { }内の処理をしたい場合には if (a?.b?.c == null) の挙動はプログラマーが意図しない判定結果になる。
従ってその場合には、以下のようにすれば良い。
// もし厳密に a.b.c == null のみを判定したいなら、 if (a != null && a.b != null && a.b.c == null) { Console.WriteLine("④a.b.c == null"); }
つまり、少々冗長になるがNull条件演算子?.は使わずに、必要な条件をずらずら並べると良い。
インデックスが範囲外かどうかの判定は?
今までは参照する予定の変数やオブジェクトが事前にNULLかどうかを判定して、それに応じて処理を分岐する手法を説明した。
では、配列変数やリスト変数をインデックス参照する場合(例 arr[n] )に、arr.Count=0 だと Out of range 例外が出る。それを避ける為には事前に Count を調べれば良いがこのインデックス範囲チェックの場合にもNULL3兄弟のような手法が使えないだろうか?
つまり事前のif判定をしなくても例外対策が出来る記述方法だ。
マイクロソフト公式サイトに以下のサンプルがある。
int? length = customers?.Length; // null if customers is null Customer first = customers?[0]; // null if customers is null int? count = customers?[0]?.Orders?.Count(); // null if customers, the first customer, or Orders is null
引用元 https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators
このサンプルだけだと分かり辛いので、Customerクラス、Orderクラスを適当に追加してC#コンソールアプリを作ってみた。
class Program { public class Order { public string item; public int price; } public class Customer { public string name; public List<Order> Orders= new List<Order>(); } static void Main(string[] args) { var customers = new Customer[] { new Customer{ name="ワイ", Orders={ new Order{ item="林檎", price=100 } } }, new Customer{ name="ワテ", Orders={ new Order{ item="饅頭", price=200 } } }, new Customer{ name="ワシ", Orders={ new Order{ item="檸檬", price=300 } } }, }; int? length = customers?.Length; // null if customers is null Customer first = customers?[0]; // null if customers is null //x customers?[0]?.Orders = null;// 代入文ではヌルナンタラ演算子たちは使えない。 customers[0].Orders = null; // 例えばここでOrdersをnullにしても↓では例外出ない。 int? count = customers?[0]?.Orders?.Count(); // null if customers, the first customer, or Orders is null } }
こんな感じか。
まあ、各自いろいろ試してみると良い。
でも余りに沢山のヌルナンタラ演算子を組み合わせると訳分からなくなる場合もあるので、そう言う場合には、素直にif文での事前チェックが良いだろう。
int?や?.や??の検索方法
例えばGoogle検索で
?.
で検索しても
Null条件演算子に関連のある情報は表示されない。
では、
C# ?.
で検索しても、C#の一般的なページしか出て来ない。
そう言う場合には、
c# question mark dot
C# クエスチョンドット
C# ハテナドット
などで検索すると「?.」がNull条件演算子(Null-conditional operators)で有る事が分る。
ヌル三兄弟の他の演算子に関しても同様である。
! (null 免除) 演算子 – ヌル四兄弟
その後、新たにヌル関連の演算子が追加された。
ヌル三兄弟がヌル四兄弟になったのだ。
四番目のヌル演算子は「null免除演算子」と言うやつでC#8.0で登場した。と言うワテも最近知ったばかりなのだが。
null免除演算子を使う簡単な例を作ってみた。
// string には null を入れる事が出来るがnullの下に波線が表示されて警告が出る。 string str1 = null;// nullの下に波線の警告が出る //その警告を消す方法がこれだ string str2 = null!;// 末尾に!(null免除演算子)を付ければ警告を消せる。 //あるいはnull許容型string?として宣言しても警告は出ない。 string? str3 = null;// string? のように明示的にnull許容型で宣言すると警告は出ない。
まあ要するにnullの警告が出た場合には、式の末尾に !(null免除演算子)を付ければ警告を消せるのだ。
ワテの場合は、従来はnull警告が出ていても単なる警告なので無視する事が多かった。
でも今後は、警告を無視せずに警告をゼロにするように !(null免除演算子)も活用したい。
まとめ
C#は使い易い言語だと思う。
当記事では、C#のヌル条件演算子(?.)、ヌル合体演算子(??)、ヌル許容型(int?)の使い方を説明した。
C#ヌル三兄弟だ(ワテの命名)。現在はヌル四兄弟だ。
C#のヌル許容型は例えて言うと JavaScript の変数みたいな感じだ。
JavaScript の変数の場合、数字でも文字列でも null でも代入出来る。
ただし、C#では int?型にはnullは代入出来るが string型は入れられない。
もしC#でも変数に数字や文字列を入れたいなら、オブジェクト型を使えばよい。
Object a = 1; a = "文字列"; object b = 1; b = "文字列";
Object と object が大文字と小文字で何が違うのかはワテは良く知らない。
どちらを使っても同じ(よう)なので、ワテの場合何となく小文字の objectを使う。
深い理由は無い。
強いて言うと、大文字の Object を使う場合には、SHIFT+o を入力するのが面倒なので。
いずれにしてもC#のオブジェクト型変数を使うと数字でも文字列でもクラスでも何でも入れられるので便利ではあるが、ワテは滅多に使わない。
やっぱり使う変数に応じてint, string, double, … のようにちゃんとした型を与えておくべきだろう。
C#の本を読む
この独習C#は昔から有名だ。
取り敢えずこう言う有名な教科書を一通りざっと目を通しておけば良い。そうすると自分でプログラムを書く時に「確かあの本にこんな手法が紹介されていたなあ」と思い出せるので。
この本も結構人気のある本のようだ。いろんな人のブログで良く見かける。でもワテは読んでいない。
Visual StudioとVisual Studio Codeは全く別物なので要注意だ。
この本は無料電話サポート付きだ。何度も電話しても良いのかどうかは未確認だが何回電話出来るのか気になるところだ。
(続く)
コメント
Object と objectの違いはなく、別名がつけられているだけですよ。
using HOGE = System.Object;
これで System.Objectクラスは 別名で HOGEって名付けることができます。
Stringクラスも同様ですね。
けいこふぉ様
この度は小生の記事にコメントありがとうございました。
今まで、Objectとobjectの違いが良く分からないまま使っていました。
別名なんですね。
疑問が解消してスッキリしました。
また何か良い情報がありましたらお教えください。