はじめに
タイトルからして、アレですが、実はコレコンパイルできます。(当然警告は出ますが)
元々、下記のようなコードを誤って書いてしまい、意図しない結果が生成されてしまったのがコトの発端でした。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication9
{
class Program
{
private static void Main(string[] args)
{
var someDictionary = new Dictionary<int, string>();
//諸々の処理。
//場合によっては、Valueにnull が入ることもある。
//本当はこう書きたかった
//foreach (var key in someDictionary.Where(elem=>elem.Value==null).Select(elem=>elem.Key))
//{
// Console.WriteLine(key);
//}
//ところがどっこいこう書いた。
foreach (var key in someDictionary.Where(elem => elem.Key == null).Select(elem => elem.Key))
{
Console.WriteLine(key);
}
}
}
}
このような場合、コンパイルエラーになるんじゃ無いかと勝手に思い込んでいたのですが、実際に、上記のコードはコンパイル可能という結果になります。
コレがどうにも直感的では無かったので、なぜ上記のようなコード、極端な例として、タイトルのようなコードがC#においてコンパイルできてしまうのか、少し調べてみました。
なぜコレがコンパイルできてしまうのか?
(2014/07/18 変更)
これは、値型もまたSystem.Objectから派生している型なので、暗黙の型変換が行われるからであると考えられます。
未解決の疑問
~~ 言語仕様を読み解きながら、なんでコレがコンパイルできてしまうのか追いかけてみた結果、
このへんてこなコードがコンパイルできるという結果に帰結したことが分かりました。
しかし、検証目的で以下のようなサンプルコードを考察した場合、未解決の疑問が残りました。
namespace ConsoleApplication9
{
public struct Equatable
{
public Equatable(int value)
: this()
{
Value = value;
}
public int Value { get; set; }
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return Value == ((Equatable) obj).Value;
}
public override int GetHashCode()
{
return Value.GetHashCode();
}
public static bool operator ==(Equatable x, Equatable y)
{
return x.Value == y.Value;
}
public static bool operator !=(Equatable x, Equatable y)
{
return x.Value != y.Value;
}
}
public struct NotEquatable
{
public NotEquatable(int value)
: this()
{
Value = value;
}
public int Value { get; set; }
}
internal class Program
{
private static void Main(string[] args)
{
var eq = new Equatable(10);
//コレは警告が出るけど通る。
var ret = eq == null;
var neq = new NotEquatable(10);
//コレはコンパイルエラー(CS0019)が発生する。
ret = neq == null;
}
}
}
上記の検証コードから、 __op ==のオーバーロードの有無__で、コンパイル可能か否かが決定されるのでは無いかと推察できます。
最後に、なぜこのような差異が発生するのか推察してみたいと思います。
おそらく、これはコンパイラの構文解析ステージで、"neq"の後に続く"=="に到達した段階で、変数neqの型NotEquatable構造体が、op==のオーバーロードを持っていないために、nullに解析が到達する以前にコンパイラエラーを発生させて居るのでは無いかと推察します。
他方、eqの方はop==の演算子オーバーライドが存在するために、nullまで構文解析されその上で、暗黙のnull許容変換、nullリテラル変換が実行されて、結果的にコンパイル可能になっているのでは無いかと推察できます。