回答:
なぜ
統一された型システムを持ち、値型が、参照型が基になるデータを表す方法とは完全に異なる基になるデータの表現を持つことができるようにします(たとえば、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
!ボクシング変換は、ボックス化されている値のコピーを作成して、動作の違いを説明します。
boxing
and のパフォーマンスへの影響についてお話しいただけますunboxing
か?
.NETフレームワークには、値のタイプと参照タイプの2種類のタイプがあります。これはオブジェクト指向言語では比較的一般的です。
オブジェクト指向言語の重要な機能の1つは、型にとらわれない方法でインスタンスを処理する機能です。これは、ポリモーフィズムと呼ばれます。ポリモーフィズムを利用したいのですが、2つの異なるタイプのタイプがあるので、それらを1つにまとめて同じ方法で処理できるようにする方法がいくつか必要です。
さて、昔(Microsoft.NETの1.0)には、この新しい汎用のジェネリックハルラルーはありませんでした。値型と参照型を処理できる単一の引数を持つメソッドを作成することはできませんでした。それはポリモーフィズムの違反です。そこで、値型をオブジェクトに強制変換する手段としてボクシングが採用されました。
これが不可能な場合、フレームワークは、他のタイプの種を受け入れることだけが目的のメソッドとクラスで散らかされます。それだけでなく、値の型は共通の型の祖先を本当に共有しないため、値の型ごとに異なるメソッドオーバーロード(ビット、バイト、int16、int32など)が必要になります。
ボクシングはこれが起こらないようにしました。 そしてそれがイギリスがボクシングデーを祝う理由です。
List<string>.Enumerator
のIEnumerator<string>
大部分のクラスタイプのように振る舞うオブジェクト収率を、しかし壊れ有するEquals
方法。にキャストList<string>.Enumerator
するより良い方法IEnumerator<string>
は、カスタム変換演算子を呼び出すことですが、暗黙の変換が存在するため、それを防ぐことができます。
これを理解する最良の方法は、C#が構築する低レベルのプログラミング言語を調べることです。
Cのような最下位レベルの言語では、すべての変数が1つの場所(スタック)に配置されます。変数を宣言するたびに、スタックに配置されます。これらは、ブール値、バイト、32ビット整数、32ビットuintなどのプリミティブ値のみにすることができます。スタックはシンプルかつ高速です。変数が追加されると、それらは順番に重なり合うだけなので、最初に宣言するのは0x00、次は0x01、次はRAMの0x02などです。さらに、変数はしばしばコンパイル時に事前にアドレス指定されます。時間なので、プログラムを実行する前でも、そのアドレスはわかっています。
C ++のような次のレベルでは、ヒープと呼ばれる2番目のメモリ構造が導入されます。あなたはまだほとんどがスタックに住んでいますが、ポインターと呼ばれる特別な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のコピーを指すスタック内のアドレスです。boolはBoxedされてそこに置かれているので、oがnullかどうかをチェックできます。
一般に、必要な場合を除いて、たとえばオブジェクトとしてint / bool / whateverを引数に渡す場合などは、ボクシングを避ける必要があります。.Netには、オブジェクトとして値型を渡すことを要求する(したがってボクシングを必要とする)いくつかの基本的な構造がありますが、ほとんどの場合、ボックス化する必要はありません。
ボクシングを必要とする過去のC#構造の網羅的ではないリスト。
イベントシステムでは、単純な使用での競合状態が発生し、非同期をサポートしていません。ボクシングの問題を追加すると、おそらく回避する必要があります。(たとえば、Genericsを使用する非同期イベントシステムに置き換えることができます。)
古いスレッディングモデルとタイマーモデルはパラメーターにボックスを強制しましたが、はるかにクリーンで効率的な非同期/待機に置き換えられました。
.Net 1.1コレクションはGenericsより前にあったため、ボクシングに完全に依存していました。これらはまだSystem.Collectionsで動き回っています。新しいコードでは、System.Collections.Genericのコレクションを使用する必要があります。これは、ボクシングを回避することに加えて、より強力な型安全性を提供します。
ボクシングを強制する上記の歴史的な問題に対処する必要があり、後でボクシングされることがわかっているときにボクシングによるパフォーマンスへの影響を回避したい場合を除き、値タイプをオブジェクトとして宣言または渡さないでください。
以下のミカエルの提案に従って:
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(その名前にもかかわらず、実際には参照型です)から派生しています。
一般に、通常は値タイプをボックス化することは避けたいでしょう。
ただし、これが役立つ状況はまれです。たとえば、1.1フレームワークをターゲットにする必要がある場合、ジェネリックコレクションにアクセスできません。.NET 1.1でコレクションを使用するには、値の型をSystem.Objectとして扱う必要があります。これにより、ボックス化/ボックス化解除が発生します。
これが.NET 2.0以降で役立つ場合はまだあります。値の型を含むすべての型を直接オブジェクトとして扱うことができるという事実を利用したいときはいつでも、ボックス化/ボックス化解除を使用する必要があるかもしれません。これは、(ジェネリックコレクションでTの代わりにオブジェクトを使用して)コレクション内の任意の型を保存できるため、時々便利ですが、一般的には、型の安全性が失われるため、これを回避することをお勧めします。ただし、ボクシングが頻繁に発生する1つのケースは、Reflectionを使用している場合です。リフレクションでの呼び出しの多くは、値のタイプを処理するときに、タイプが事前にわからないため、ボクシング/アンボクシングが必要になります。
ボクシングとは、ヒープ上のオブジェクトのあるオフセットのデータを使用して、値を参照型に変換することです。
ボクシングが実際に行うことについて。下記は用例です
モノC ++
void* mono_object_unbox (MonoObject *obj)
{
MONO_EXTERNAL_ONLY_GC_UNSAFE (void*, mono_object_unbox_internal (obj));
}
#define MONO_EXTERNAL_ONLY_GC_UNSAFE(t, expr) \
t result; \
MONO_ENTER_GC_UNSAFE; \
result = expr; \
MONO_EXIT_GC_UNSAFE; \
return result;
static inline gpointer
mono_object_get_data (MonoObject *o)
{
return (guint8*)o + MONO_ABI_SIZEOF (MonoObject);
}
#define MONO_ABI_SIZEOF(type) (MONO_STRUCT_SIZE (type))
#define MONO_STRUCT_SIZE(struct) MONO_SIZEOF_ ## struct
#define MONO_SIZEOF_MonoObject (2 * MONO_SIZEOF_gpointer)
typedef struct {
MonoVTable *vtable;
MonoThreadsSync *synchronisation;
} MonoObject;
Monoでのボックス化解除は、オブジェクトの2 gポインターのオフセット(16バイトなど)にポインターをキャストするプロセスです。A gpointer
はvoid*
です。これはMonoObject
明らかにデータのヘッダーにすぎないので、の定義を見ると理にかなっています。
C ++
C ++で値をボックス化するには、次のようにします。
#include <iostream>
#define Object void*
template<class T> Object box(T j){
return new T(j);
}
template<class T> T unbox(Object j){
T temp = *(T*)j;
delete j;
return temp;
}
int main() {
int j=2;
Object o = box(j);
int k = unbox<int>(o);
std::cout << k;
}