C#でボックス化とボックス化解除が必要なのはなぜですか?


324

C#でボックス化とボックス化解除が必要なのはなぜですか?

私はボクシングとアンボクシングが何であるかを知っていますが、それの実際の使用を理解することはできません。なぜ、どこで使用する必要がありますか?

short s = 25;

object objshort = s;  //Boxing

short anothershort = (short)objshort;  //Unboxing

回答:


481

なぜ

統一された型システムを持ち、値型が、参照型が基になるデータを表す方法とは完全に異なる基になるデータの表現を持つことができるようにします(たとえば、int参照とは完全に異なる32ビットのバケットです)タイプ)。

このように考えてください。oタイプの変数がありますobject。そして今、あなたはintそれをに入れたいと思っていますooどこかへの参照であり、どこかへの参照でintはないことをはっきりと示しています(結局のところ、それは単なる数字です)。だから、あなたがすることはこれです:をobject保存できる新しいものを作り、int次にそのオブジェクトへの参照をに割り当てますo。このプロセスを「ボクシング」と呼びます。

したがって、統一された型システムを使用する必要がない場合(つまり、参照型と値型の表現が大きく異なり、2つを「表現する」共通の方法が必要ない場合)、ボックス化は必要ありません。intそれらの基礎となる値を表すことを気にしない場合(つまり、代わりintに参照型でもあり、それらの基礎となる値への参照を格納するだけ)、ボクシングは必要ありません。

どこで使用すればよいですか。

たとえば、古いコレクションタイプArrayListobjects しか食べません。つまり、どこかに住んでいるものへの参照のみを保存します。ボクシングなしでは、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);

次の文に進む前に、少し考えてみてください。

あなたが言ったTrueFalseすごい!待って、何?これは==、参照型では、基になる値が等しいかどうかではなく、参照が等しいかどうかをチェックする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);

出力は何ですか?場合によります!場合Pointstruct、出力はある1ものの場合Pointclass、出力はあり2!ボクシング変換は、ボックス化されている値のコピーを作成して、動作の違いを説明します。


@ジェイソンプリミティブリストがある場合、ボクシング/アンボクシングを使用する理由はないということですか?
Pacerier 2012年

「プリミティブリスト」の意味がわかりません。
ジェイソン2012年

3
boxingand のパフォーマンスへの影響についてお話しいただけますunboxingか?
ケビンメレディス2013年


2
優れた答え-よく評価された本で読んだほとんどの説明よりも優れています。
FredM 2016

58

.NETフレームワークには、値のタイプと参照タイプの2種類のタイプがあります。これはオブジェクト指向言語では比較的一般的です。

オブジェクト指向言語の重要な機能の1つは、型にとらわれない方法でインスタンスを処理する機能です。これは、ポリモーフィズムと呼ばれます。ポリモーフィズムを利用したいのですが、2つの異なるタイプのタイプがあるので、それらを1つにまとめて同じ方法で処理できるようにする方法がいくつか必要です。

さて、昔(Microsoft.NETの1.0)には、この新しい汎用のジェネリックハルラルーはありませんでした。値型と参照型を処理できる単一の引数を持つメソッドを作成することはできませんでした。それはポリモーフィズムの違反です。そこで、値型をオブジェクトに強制変換する手段としてボクシングが採用されました。

これが不可能な場合、フレームワークは、他のタイプの種を受け入れることだけが目的のメソッドとクラスで散らかされます。それだけでなく、値の型は共通の型の祖先を本当に共有しないため、値の型ごとに異なるメソッドオーバーロード(ビット、バイト、int16、int32など)が必要になります。

ボクシングはこれが起こらないようにしました。 そしてそれがイギリスがボクシングデーを祝う理由です。


1
ジェネリックの前は、オートボクシングは多くのことを行うために必要でした。ただし、ジェネリックの存在を考えると、古いコードとの互換性を維持する必要がない場合は、.netは暗黙のボクシング変換なしで使用した方がよいと思います。以下のような値型キャストList<string>.EnumeratorIEnumerator<string>大部分のクラスタイプのように振る舞うオブジェクト収率を、しかし壊れ有するEquals方法。にキャストList<string>.Enumeratorするより良い方法IEnumerator<string>は、カスタム変換演算子を呼び出すことですが、暗黙の変換が存在するため、それを防ぐことができます。
スーパーキャット2012

42

これを理解する最良の方法は、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にエイリアスしますが、機能上の違いはありません。


4
あなたは、何百人ものプログラマーやITプロフェッショナルが何年も説明できないことを私に教えましたが、従うのが少し難しくなったので、避けるべきことの代わりに何をすべきかを言うように変更しました。 。あなたはこれを行わないでください、代わりにこれを行ってください
ミカエルプサーリ2016年

2
この答えは何百回もの回答としてマークされているはずです!
Pouyan

3
C#には「Int」はなく、intとInt32があります。私はあなたが一方が値型であり、もう一方が値型をラップする参照型であると述べるのは間違っていると思います。私が間違っていない限り、それはJavaには当てはまりますが、C#には当てはまりません。C#では、IDEで青く表示されるのは、構造体定義のエイリアスです。つまり、int = Int32、bool =ブール、string =文字列です。ブール値ではなくブール値を使用する理由は、MSDNの設計ガイドラインと規則でそのように推奨されているためです。そうでなければ私はこの答えが大好きです。しかし、私が間違っていることを証明するか、回答でそれを修正するまで、私は反対票を投じます。
Heriberto Lugo

2
変数をintとして宣言し、別の変数をInt32、またはブールとブールとして宣言する場合-右クリックして定義を表示すると、構造体に対して同じ定義になります。
Heriberto Lugo

2
@HeribertoLugoは正しいです。「値型をブールではなくブールとして宣言することは避けてください」という行が誤っています。OPが指摘するように、ブール(またはブール、またはその他の値タイプ)をオブジェクトとして宣言しないでください。BOOL /ブール、INT /のInt32、C#と.NETの間だけの別名である:docs.microsoft.com/en-us/dotnet/csharp/language-reference/...
STW

21

ボクシングは実際に使用するものではありません。ランタイムとは、参照と値の型を必要に応じて同じ方法で処理できるようにするために使用するものです。たとえば、ArrayListを使用して整数のリストを保持している場合、整数はArrayListのオブジェクトタイプのスロットに収まるようにボックス化されます。

ジェネリックコレクションを使用すると、これはほとんどなくなります。を作成する場合List<int>、ボクシングは行われません- List<int>は整数を直接保持できます。


複合文字列の書式設定などには、まだボクシングが必要です。ジェネリックを使用しているときはあまり見かけないかもしれませんが、それでも確実に存在します。
ジェレミーS

1
true-ADO.NETでも常に表示されます-SQLパラメータ値は、実際のデータ型に関係なく、すべて「オブジェクト」です
Ray

11

ボックス化とボックス化解除は、値タイプのオブジェクトを参照タイプとして扱うために特に使用されます。実際の値をマネージヒープに移動し、参照によって値にアクセスする。

ボクシングとアンボクシングなしでは、参照によって値タイプを渡すことはできません。つまり、値の型をオブジェクトのインスタンスとして渡すことができませんでした。


ほぼ10年後も素晴らしい回答+ 1 + 1
snr

1
数値型の参照渡しはボクシングなしの言語に存在し、他の言語は値型をボクシングなしのオブジェクトのインスタンスとして扱い、値をヒープに移動します(たとえば、ポインターが4バイト境界に揃えられている動的言語の実装では、下位4つを使用します)値が完全なオブジェクトではなく整数またはシンボルであることを示す参照のビット。このような値の型は不変であり、ポインターと同じサイズです)。
ピートカーカム

8

何かのボックスを解除する必要があった最後の場所は、データベースからデータを取得するコードを作成したときでした(私はLINQ to SQL使用していませんでした。単純に古いADO.NETです)。

int myIntValue = (int)reader["MyIntValue"];

基本的に、ジェネリックの前に古いAPIを使用している場合は、ボクシングが発生します。それ以外は、それほど一般的ではありません。


4

オブジェクトをパラメーターとして必要とする関数がある場合、ボクシングが必要ですが、渡す必要がある値タイプが異なる場合は、関数に渡す前に、まず値タイプをオブジェクトデータタイプに変換する必要があります。

私はそれが本当だとは思わない、代わりにこれを試してください:

class Program
    {
        static void Main(string[] args)
        {
            int x = 4;
            test(x);
        }

        static void test(object o)
        {
            Console.WriteLine(o.ToString());
        }
    }

私はボクシング/アンボクシングを使用しませんでした。(コンパイラが裏でそれを行わない限り?)


これは、すべてがSystem.Objectから継承され、メソッドに追加情報を含むオブジェクトを与えているため、基本的には、期待しているものと、特に何も期待していないため、期待しているものでテストメソッドを呼び出しているためです。.NETの多くは舞台裏で行われ、それが非常にシンプルな言語である理由
Mikael Puusaari

1

.netでは、Objectのすべてのインスタンス、またはそこから派生したタイプには、そのタイプに関する情報を含むデータ構造が含まれます。.netの「実際の」値タイプには、そのような情報は含まれていません。オブジェクトから派生した型を受け取ることを期待するルーチンが値型のデータを操作できるようにするために、システムは各値型に対して、同じメンバーとフィールドを持つ対応するクラス型を自動的に定義します。ボクシングは、このクラス型の新しいインスタンスを作成し、値型インスタンスからフィールドをコピーします。ボックス化を解除すると、クラスタイプのインスタンスから値タイプのインスタンスにフィールドがコピーされます。値型から作成されるすべてのクラス型は、皮肉な名前のクラスValueType(その名前にもかかわらず、実際には参照型です)から派生しています。


0

メソッドが参照型をパラメーターとしてのみ受け取る場合(たとえば、制約を介してクラスになるように制約されたジェネリックメソッド)、参照型をメソッドnewに渡してボックス化する必要はありません。

また、これは取る任意のメソッドの真であるobjectパラメータとして-これがします持っている参照型であることを。


0

一般に、通常は値タイプをボックス化することは避けたいでしょう。

ただし、これが役立つ状況はまれです。たとえば、1.1フレームワークをターゲットにする必要がある場合、ジェネリックコレクションにアクセスできません。.NET 1.1でコレクションを使用するには、値の型をSystem.Objectとして扱う必要があります。これにより、ボックス化/ボックス化解除が発生します。

これが.NET 2.0以降で役立つ場合はまだあります。値の型を含むすべての型を直接オブジェクトとして扱うことができるという事実を利用したいときはいつでも、ボックス化/ボックス化解除を使用する必要があるかもしれません。これは、(ジェネリックコレクションでTの代わりにオブジェクトを使用して)コレクション内の任意の型を保存できるため、時々便利ですが、一般的には、型の安全性が失われるため、これを回避することをお勧めします。ただし、ボクシングが頻繁に発生する1つのケースは、Reflectionを使用している場合です。リフレクションでの呼び出しの多くは、値のタイプを処理するときに、タイプが事前にわからないため、ボクシング/アンボクシングが必要になります。


0

ボクシングとは、ヒープ上のオブジェクトのあるオフセットのデータを使用して、値を参照型に変換することです。

ボクシングが実際に行うことについて。下記は用例です

モノ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 gpointervoid*です。これは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;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.