C#でボックス化とボックス化解除が必要なのはなぜですか?
ボクシングとボックス解除が何であるかは知っていますが、実際の使用法を理解することはできません。なぜ、どこで使用する必要がありますか?
short s = 25;
object objshort = s; //Boxing
short anothershort = (short)objshort; //Unboxing
なぜ
統一された型システムを持ち、参照型が基になるデータを表す方法と値型が基になるデータの完全に異なる表現を持つことができるようにする(たとえば、int
は32ビットのバケットであり、参照タイプ)。
このように考えてください。タイプo
の変数object
があります。そして今、int
があり、それをo
に入れたいと思います。 o
はどこかへの参照であり、int
はどこかへの参照ではありません(結局、単なる数字です)。したがって、あなたがすることはこれです:object
を格納できる新しいint
を作成し、そのオブジェクトへの参照をo
に割り当てます。このプロセスを「ボクシング」と呼びます。
したがって、統一された型システムを持つことを気にしない場合(つまり、参照型と値型は非常に異なる表現を持ち、2つを「表現する」一般的な方法が必要ない場合)、ボクシングは必要ありません。 int
が基になる値を表すことを気にしない場合(つまり、代わりにint
を参照型にして、基になる値への参照を保存するだけ)、ボクシングは必要ありません。
どこで使用すればいいですか。
たとえば、古いコレクション型ArrayList
はobject
sのみを消費します。つまり、どこかに存在するものへの参照のみを保存します。ボクシングなしでは、int
をそのようなコレクションに入れることはできません。しかし、ボクシングを使えばできます。
さて、ジェネリック医薬品の時代には、これを本当に必要とせず、一般的に問題を考えずに楽に進めることができます。ただし、注意すべきいくつかの注意事項があります。
これは正しいです:
double e = 2.718281828459045;
int ee = (int)e;
これではありません:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
代わりに、これを行う必要があります。
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
最初にdouble
((double)o
)を明示的にアンボックス化してから、それをint
にキャストする必要があります。
次の結果は何ですか:
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
次の文に進む前に、少し考えてみてください。
True
およびFalse
と言ったら素晴らしい!待って、何?これは、参照型の==
が、基礎となる値が等しいかどうかではなく、参照が等しいかどうかを確認するreference-equalityを使用するためです。これは危険なほど簡単な間違いです。おそらくさらに微妙な
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
False
も出力します!
言う方が良い:
Console.WriteLine(o1.Equals(o2));
ありがたいことに、True
を出力します。
最後の微妙な点:
[struct|class] Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(1, 1);
object o = p;
p.x = 2;
Console.WriteLine(((Point)o).x);
出力は何ですか?場合によります! Point
がstruct
の場合、出力は1
ですが、Point
がclass
の場合、出力は2
です!ボクシング変換は、動作の違いを説明するボックス化される値のコピーを作成します。
.NETフレームワークには、値の型と参照型の2種類の型があります。これは、OO言語では比較的一般的です。
オブジェクト指向言語の重要な機能の1つは、型に依存しない方法でインスタンスを処理できることです。これは polymorphism と呼ばれます。ポリモーフィズムを利用したいのですが、2つの異なるタイプのタイプがあるため、どちらかを同じ方法で処理できるように、それらをまとめる何らかの方法が必要です。
さて、昔(Microsoft.NETの1.0)に戻って、この新たなジェネリックジェネラルハラバルーはありませんでした。値型と参照型を処理できる単一の引数を持つメソッドを書くことはできませんでした。それはポリモーフィズムの違反です。そのため、値型をオブジェクトに強制する手段としてボクシングが採用されました。
これが不可能な場合、フレームワークには、他の種類の型を受け入れることが唯一の目的であるメソッドとクラスが散らばっています。それだけでなく、値の型は実際に共通の型の祖先を共有しないため、値の型(ビット、バイト、int16、int32など)ごとに異なるメソッドのオーバーロードが必要になります。
ボクシングはこれが起こらないようにしました。 そして、それがイギリス人がボクシングデーを祝う理由です。
これを理解する最良の方法は、C#ビルドの下位レベルのプログラミング言語を調べることです。
Cのような最低レベルの言語では、すべての変数が1つの場所、つまりスタックに移動します。変数を宣言するたびに、スタックに移動します。これらは、bool、byte、32ビットint、32ビットuintなどのプリミティブ値のみです。スタックはシンプルで高速です。変数が追加されると、変数は次々に重なり合うので、最初に宣言するのは0x00、次は0x01、次は0x02、RAMなどです。さらに、変数はコンパイル時に事前にアドレス指定されることがよくあります。時間なので、プログラムを実行する前にアドレスがわかっています。
次のレベルでは、C++のように、ヒープと呼ばれる2番目のメモリ構造が導入されます。まだほとんどスタックに住んでいますが、Pointersと呼ばれる特別なintをスタックに追加して、オブジェクトの最初のバイトのメモリアドレスを保存できます、およびそのオブジェクトはヒープ内に存在します。 Stackは、スタック変数とは異なり、プログラムの実行中に直線的に上下に積み重ならないため、ヒープは一種の混乱であり、維持するのにいくらか費用がかかります。それらは特定の順序で行ったり来たりすることはできず、成長および縮小することができます。
ポインターを扱うのは難しいです。メモリリーク、バッファオーバーラン、フラストレーションの原因です。 C#が助けになります。
より高いレベルのC#では、ポインターについて考える必要はありません-.Netフレームワーク(C++で記述されている)はこれらをユーザーに代わって考慮し、オブジェクトへの参照として提示します。パフォーマンスのために、より単純な値を保存できます値型としてのブール、バイト、整数など。ボンネットの下では、クラスをインスタンス化するオブジェクトやものは高価なメモリ管理ヒープに置かれ、値タイプは低レベルCにあった同じスタックに入れられます-超高速です。
これら2つの根本的に異なるメモリの概念(およびストレージの戦略)間の相互作用をコーダーの観点から単純に保つために、値の型はいつでもボックス化できます。 ボックス化により、値がスタックからコピーされ、オブジェクトに入れられ、ヒープに配置されます -より高価ですが、参照世界との流動的な相互作用。他の答えが指摘しているように、これは例えばあなたが言うとき起こります:
bool b = false; // Cheap, on Stack
object o = b; // Legal, easy to code, but complex - Boxing!
bool b2 = (bool)o; // Unboxing!
ボクシングの利点の強力な例は、nullのチェックです。
if (b == null) // Will not compile - bools can't be null
if (o == null) // Will compile and always return false
オブジェクトoは、厳密には、ヒープにコピーされたbool bのコピーを指すStack内のアドレスです。ブール値がボックス化され、そこに配置されるため、nのoをチェックできます。
一般に、必要に応じてボクシングを避ける必要があります。たとえば、int/bool/whateverをオブジェクトとして引数に渡す場合です。 .Netには、オブジェクトとして値型を渡すことを要求する(したがって、ボクシングを必要とする)基本構造がいくつかありますが、ほとんどの場合、決してBoxする必要はありません。
Boxingを必要とする歴史的なC#構造の完全ではないリスト、あなたは避けるべきです:
イベントシステム 結局、競合状態になります それを単純に使用し、非同期をサポートしていません。ボクシングの問題を追加し、おそらく回避する必要があります。 (たとえば、Genericsを使用する非同期イベントシステムに置き換えることができます。)
古いスレッディングモデルとタイマーモデルは、パラメーターにボックスを強制していましたが、はるかにクリーンで効率的なasync/awaitに置き換えられました。
.Net 1.1コレクションは、ジェネリックよりも前に登場したため、ボクシングに完全に依存していました。これらはまだSystem.Collectionsで動き回っています。新しいコードでは、System.Collections.Genericのコレクションを使用する必要があります。これは Boxingを回避することに加えて、より強力な型安全性も提供します です。
Boxingを強制する上記の歴史的な問題に対処する必要がない限り、Value Typeをオブジェクトとして宣言したり、渡したりすることは避けてください。
以下のミカエルの提案:
using System.Collections.Generic;
var employeeCount = 5;
var list = new List<int>(10);
using System.Collections;
Int32 employeeCount = 5;
var list = new ArrayList(10);
この回答はもともと、Int32、Boolなどがボクシングを引き起こすことを示唆していましたが、実際には値タイプの単純なエイリアスです。つまり、.NetにはBool、Int32、Stringなどの型があり、C#はそれらをbool、int、stringにエイリアスし、機能的な違いはありません。
ボクシングは実際には使用するものではありません。必要に応じて同じ方法で参照型と値型を処理できるようにランタイムが使用するものです。たとえば、整数のリストを保持するためにArrayListを使用した場合、整数はArrayListのオブジェクト型スロットに収まるようにボックス化されます。
汎用コレクションを使用すると、これはほとんどなくなります。 List<int>
を作成する場合、ボクシングは行われません-List<int>
は整数を直接保持できます。
ボクシングとアンボクシングは、特に値型オブジェクトを参照型として扱うために使用されます。実際の値をマネージヒープに移動し、参照によって値にアクセスします。
ボックス化とボックス化解除なしでは、参照によって値型を渡すことはできません。つまり、値型をオブジェクトのインスタンスとして渡すことができませんでした。
私が最後に何かを開封しなければならなかったのは、データベースからいくつかのデータを取得するコードを書くときでした(私は LINQ to SQL を使用していませんでした、ただ古い ADO.NET ):
int myIntValue = (int)reader["MyIntValue"];
基本的に、ジェネリックの前に古いAPIを使用している場合、ボクシングが発生します。それ以外は、それほど一般的ではありません。
パラメータとしてオブジェクトを必要とする関数がある場合、ボクシングが必要ですが、渡す必要がある異なる値型があります。その場合、関数に渡す前に、まず値型をオブジェクトデータ型に変換する必要があります。
私はそれが本当だとは思わない、代わりにこれを試してください:
class Program
{
static void Main(string[] args)
{
int x = 4;
test(x);
}
static void test(object o)
{
Console.WriteLine(o.ToString());
}
}
それはうまく動作し、ボクシング/アンボクシングは使用しませんでした。 (コンパイラが舞台裏でそれをしない限り?)
.netでは、Objectのすべてのインスタンス、またはそれから派生したタイプには、そのタイプに関する情報を含むデータ構造が含まれています。 .netの「実際の」値タイプには、そのような情報は含まれません。値型のデータを、オブジェクトから派生した型を受け取ることを期待するルーチンで操作できるようにするために、システムは、値型ごとに、同じメンバーとフィールドを持つ対応するクラス型を自動的に定義します。ボクシングは、このクラスタイプの新しいインスタンスを作成し、値タイプのインスタンスからフィールドをコピーします。ボックス化解除は、フィールドをクラス型のインスタンスから値型のインスタンスにコピーします。値型から作成されるすべてのクラス型は、皮肉な名前が付けられたクラスValueType(名前にもかかわらず、実際には参照型です)から派生します。
メソッドがパラメーターとして参照型のみをとる場合(たとえば、new
制約を介してクラスに制約される汎用メソッド)、参照型を渡すことができず、ボックス化する必要があります。
これは、object
をパラメーターとして使用するすべてのメソッドにも当てはまります。これは、参照タイプになるhaveになります。
一般に、通常、値型をボックス化することは避けたいでしょう。
ただし、これが役立つ場合はまれにしか発生しません。たとえば、1.1フレームワークをターゲットにする必要がある場合、汎用コレクションにアクセスできません。 .NET 1.1でコレクションを使用するには、値の型をSystem.Objectとして扱う必要があり、これによりボックス化/ボックス化解除が発生します。
.NET 2.0+でこれが役立つ場合がまだあります。値型を含むすべての型をオブジェクトとして直接扱うことができるという事実を利用したいときはいつでも、ボクシング/アンボクシングを使用する必要があるかもしれません。これは、コレクションに任意のタイプを保存できるため(汎用コレクションでTの代わりにオブジェクトを使用することにより)便利な場合がありますが、一般に、タイプセーフを失うため、これを避ける方が良いです。ただし、ボクシングが頻繁に発生するケースの1つは、Reflectionを使用している場合です。リフレクションでの呼び出しの多くは、タイプが事前にわからないため、値タイプを操作するときにボクシング/アンボクシングを必要とします。