Edited at

C#で「person?.Name」と「person == null ? null : person.Name」は等価じゃない。

More than 1 year has passed since last update.

C#において

person?.Name」と「person == null ? null : person.Name」は等価じゃありません。

違う見方をすると、

null条件演算子 (?.) において、nullとの比較に 「==」オペレーターでの比較は行われません。(型に対して==で独自定義した処理は呼ばれない)

同様に、null合体演算子 (??)でも同じように、nullとの比較に 「==」オペレーターでの比較は行われません。(型に対して==で独自定義した処理は呼ばれない)


再現・確認コード

これを再現・確認できるコードは次の通りです。

// 本当はEqualsとかも実装するべき

class Person {
public string Name { get; set; }

public static bool operator ==(Person lhs, Person rhs) {
Console.WriteLine("called ==");
return object.ReferenceEquals(lhs, rhs);
}

public static bool operator !=(Person lhs, Person rhs) {
Console.WriteLine("called !=");
return !object.ReferenceEquals(lhs, rhs);
}
}

void Main()
{
Person personNonNull = new Person { Name = "Taro" };
Console.WriteLine(personNonNull?.Name);

Person personNull = null;
Console.WriteLine(personNull?.Name);
}

このコードを実行すると、「called ==」というログは表示されません。==オペレーターが呼ばれていないということが確認できますね。


ILの確認 ==を使った場合

void Main()

{
Person person = new Person { Name = "Taro" };
string name = person == null ? null : person.Name;
Console.WriteLine();
}

このコードのILは、こんな感じ。

IL_0000:  nop         

IL_0001: newobj UserQuery+Person..ctor
IL_0006: dup
IL_0007: ldstr "Taro"
IL_000C: callvirt UserQuery+Person.set_Name
IL_0011: nop
IL_0012: stloc.0 // person
IL_0013: ldloc.0 // person
IL_0014: ldnull
IL_0015: call UserQuery+Person.op_Equality
IL_001A: brtrue.s IL_0024
IL_001C: ldloc.0 // person
IL_001D: callvirt UserQuery+Person.get_Name
IL_0022: br.s IL_0025
IL_0024: ldnull
IL_0025: stloc.1 // name
IL_0026: call System.Console.WriteLine
IL_002B: nop
IL_002C: ret

IL_0015:  call        UserQuery+Person.op_Equality

に注目してください。 ==の実装が呼ばれていますね。



ILの確認 null条件演算子を使った場合

void Main()

{
Person person = new Person { Name = "Taro" };
string name = person?.Name;
Console.WriteLine();
}

このコードのILは、こんな感じ。

IL_0000:  nop         

IL_0001: newobj UserQuery+Person..ctor
IL_0006: dup
IL_0007: ldstr "Taro"
IL_000C: callvirt UserQuery+Person.set_Name
IL_0011: nop
IL_0012: stloc.0 // person
IL_0013: ldloc.0 // person
IL_0014: brtrue.s IL_0019
IL_0016: ldnull
IL_0017: br.s IL_001F
IL_0019: ldloc.0 // person
IL_001A: call UserQuery+Person.get_Name
IL_001F: stloc.1 // name
IL_0020: call System.Console.WriteLine
IL_0025: nop
IL_0026: ret

UserQuery+Person.op_Equalityが存在しないことに注目してください。==で定義したした処理は呼ばれないことに注目してください。


まとめ

person?.Name」と「person == null ? null : person.Name」は等価じゃありません。

違う見方をすると、

null条件演算子 (?.) において、nullとの比較に 「==」オペレーターでの比較は行われない。(型に対して==で独自定義した処理は呼ばれない)

同様に、null合体演算子 (??)でも同じように、nullとの比較に 「==」オペレーターでの比較は行われない。(型に対して==で独自定義した処理は呼ばれない)


こちらもどうぞ

関連記事 : Reshaper/Riderの「Possible unintended bypass of lifetime check of underlying Unity engine object」って何ぞや?