泥庭

2015年7月23日

【C#6.0】null条件演算子

Filed under: C#, VisualStudio — タグ: , , — yone64 @ 12:15 PM
さて今回は、多くの人が待ち望んでいた(?)null条件演算子ですよっと。
# 便利だけど割と難しい

null条件演算子は、”?”以前がnullかどうかを確認して、nullじゃない場合は後の呼び出しを実行、nullの場合はnullを返す。
という、動きをします。
var a = default(string);

// メソッド呼び出し
var b1 = a?.ToUpper();
// 以下と同値
// var b1 = a != null ? a.ToUpper() : null;

// プロパティーアクセス
var b2 = a?.Length;

// インデックスアクセス
var b3 = a?[0];

// デリゲートの場合はちょっとだけ異なり
// Invokeメソッド呼び出しを明示的に記述する必要がある。
Func<int, int> f = i => i;
var b4 = f?.Invoke(0);

// もちろん連続して使えます。
var b5 = a?.ToUpper()?.Trim()?[0];
nullチェックが不要になって、短くかけていい感じですね。
上記でさらっと使ってしまいましたが、structの場合はnullにならないよね?という、疑問はあって当然ですよね。
先に答えを言ってしまうと、structの場合はnullable型が返ります。
// 先ほどの例でいうと、つまりはこういうこと
var b2 = a?.Length; // b2はint?型
var b3 = a?[0];     // b3はchar?型
この「?」の前が、classかstructかで戻値が変わるという挙動が微妙に難しい。
ところで、structの場合はちょっと直観的でない動きをします。
// 個別に書くと↓となるので
var a = default(string);
var b2 = a?.Length;         // b2はint?型
var c2 = b2?.ToString();    // c2はstring型 

// つなげて書くと↓になりそうなのだが、コンパイルエラー
//var c3 = a?.Length?.ToString();

// 正しくは
var c3 = a?.Length.ToString();

// 内部的には↓のように展開されます。
var c4 = a != null ? a.Length.ToString() : null;
// ↓と同義ではないことに注意。結果は一緒ですが...
var c5 = (a?.Length)?.ToString();
a != nullの時点でa.Lengthがnullになることはないので、とっても正しいんですが、ちょっと直観的でない気がします。
さて、structとclassで挙動が変わるのであれば、もう一つ気になることがでてきますよね?
そうgeneric周りの動作です。
public class Fuga
{
    public void Piyo<T>(List<T> list)
    {
        // bの型が決まらないので、コンパイルエラー
        // var b = list?[0];   
    }

    public void PiyoS<T>(List<T> list) where T : struct
    {
        var b = list?[0];   // bはT?型
    }
    public void PiyoC<T>(List<T> list) where T : class
    {
        var b = list?[0];   // bはT型
    }
}
変数の型が決まらない場合はコンパイルエラーになるので、適宜structなりclassなりの型制約を付けてあげましょうね。という話でした。なお、C#では型制約だけの異なるオーバーロードが作れないので、ご注意ください。
さらに気持ち悪いというか、便利(!?)な仕様があって、型が決まればコンパイルが通るんですよねw
public class Fuga
{
    public void Piyo<T>(List<T> list)
    {
        // bはstring型に決まるので、コンパイルが通る!!
        // インデクサアクセスの後に[?]が要るのも微妙。
        var b = list?[0]?.ToString();
        Console.WriteLine(b);
    }
}
うん。気持ち悪い。ちょっとこれは内部実装機になりますよね。(ならない?
そこでILSpyの登場です。
public unsafe void Piyo<T>(List<T> list)
{
	string arg_40_0;
	if (list == null)
	{
		arg_40_0 = null;
	}
	else
	{
		T t = list[0];
		T* arg_35_0 = ref t;
		T t2 = default(T);
		if (t2 == null)
		{
			t2 = t;
			arg_35_0 = ref t2;
			if (t2 == null)
			{
				arg_40_0 = null;
				goto IL_40;
			}
		}
		arg_40_0 = arg_35_0.ToString();
	}
	IL_40:
	string value = arg_40_0;
	Console.WriteLine(value);
}
unsafeだと!?。という文法に展開されてました。
後、default(T)がnullかどうかでstructとclass分けてるのね。
広告

コメントする »

まだコメントはありません。

RSS feed for comments on this post. TrackBack URI

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

WordPress.com で無料サイトやブログを作成.

%d人のブロガーが「いいね」をつけました。