次のコードを検討してください。
_int? x = null;
Console.Write ("Hashcode: ");
Console.WriteLine(x.GetHashCode());
Console.Write("Type: ");
Console.WriteLine(x.GetType());
_
実行されると、Hashcodeは_0
_であると書き込みますが、NullReferenceException
のタイプを判別しようとしてx
で失敗します。 nullable型で呼び出されるメソッドは、実際には基になる値で呼び出されることを知っているので、x.GetHashCode()
の間にプログラムが失敗することを期待していました。
それでは、これら2つの方法の根本的な違いは何ですか?なぜ最初の方法が失敗しないのですか?
これは、int? x = null;
が本質的に値タイプSystem.Nullable<int>
のインスタンスを、「内部」null
値で作成するためです(.HasVaue
プロパティで確認できます)。 GetHashCode
が呼び出されると、オーバーライドNullable<int>.GetHashCode
がメソッド候補(メソッドが仮想であるため)になり、Nullable<int>
のインスタンスを取得し、そのインスタンスメソッドを完全に実行します。
GetType
を呼び出すと、メソッドは非仮想であるため、Nullable<int>
のインスタンスは、 ドキュメント およびボックス化された値に従って、最初にSystem.Object
にボックス化されます。 null
であるため、NullReferenceException
です。
ダニー・チェンの正解を明確にするには:
Nullable<T>
は値型です。値の型は、nullを示すブール(falseはnullを意味する)とT(値)で構成されます。Nullable<T>
にボックス化されません。ボックス化されたT
またはnull参照のいずれかにボックス化します。S
で実装されたメソッドは、見えないref S
引数を持っているかのように実装されます。これがthis
が渡される方法です。C
によって実装されるメソッドは、見えないC
引数が存在するかのように実装されます。これがthis
が渡される方法です。これで、何が起こるかを推測するのに十分な情報が得られました。 GetHashCode
は仮想およびNullable<T>
でオーバーライドされますですので、呼び出すときは、this
に不可視のref Nullable<T>
引数があったかのように呼び出します。ボクシングは発生しません。
GetType
は仮想ではないため、オーバーライドできず、object
で定義されます。そのため、object
に対してthis
が必要です。Nullable<T>
で呼び出された場合、レシーバーはボックス化する必要があり、そのためnullにボックス化できるため、スローできます。
((object)x).GetHashCode()
を呼び出した場合、例外が表示されます。
Nullable<T>.GetHashCode()
の実装は次のとおりです。
_public override int GetHashCode()
{
if (!this.HasValue)
{
return 0;
}
return this.value.GetHashCode();
}
_
そのため、値がnullの場合、常に_0
_が取得されます。
x.GetType()
はnull.GetType()
と同じで、_Object reference not set to an instance of an object
_をスローします
GetHashCodeがnullチェックを取得しているようです。 (JetBrainsを使用して定義を表示)
public override int GetHashCode()
{
if (!this.hasValue)
return 0;
return this.value.GetHashCode();
}