C#でのボクシングの発生


86

私はボクシングがC#で発生するすべての状況を収集しようとしています:

  • 値型から型への変換System.Object

    struct S { }
    object box = new S();
    
  • 値型から型への変換System.ValueType

    struct S { }
    System.ValueType box = new S();
    
  • 列挙型の値を型に変換するSystem.Enum

    enum E { A }
    System.Enum box = E.A;
    
  • 値型をインターフェース参照に変換する:

    interface I { }
    struct S : I { }
    I box = new S();
    
  • C#文字列連結での値型の使用:

    char c = F();
    string s1 = "char value will box" + c;
    

    注:char型の定数はコンパイル時に連結されます

    注:バージョン6.0 C#コンパイラ以降に最適化し連結関与boolcharIntPtrUIntPtr種類

  • 値型インスタンスメソッドからデリゲートを作成する:

    struct S { public void M() {} }
    Action box = new S().M;
    
  • 値型でオーバーライドされていない仮想メソッドを呼び出す:

    enum E { A }
    E.A.GetHashCode();
    
  • is式の下でC#7.0定数パターンを使用する:

    int x = …;
    if (x is 42) { … } // boxes both 'x' and '42'!
    
  • C#タプルタイプ変換でのボクシング:

    (int, byte) _tuple;
    
    public (object, object) M() {
      return _tuple; // 2x boxing
    }
    
  • object値型のデフォルト値を持つ型のオプションのパラメーター:

    void M([Optional, DefaultParameterValue(42)] object o);
    M(); // boxing at call-site
    
  • 制約のないジェネリック型の値をチェックしていますnull

    bool M<T>(T t) => t != null;
    string M<T>(T t) => t?.ToString(); // ?. checks for null
    M(42);
    

    注:これは、一部の.NETランタイムでJITによって最適化される場合があります

  • /演算子を使用した制約なしまたはstructジェネリック型の型テスト値:isas

    bool M<T>(T t) => t is int;
    int? M<T>(T t) => t as int?;
    IEquatable<T> M<T>(T t) => t as IEquatable<T>;
    M(42);
    

    注:これは、一部の.NETランタイムでJITによって最適化される場合があります

あなたが知っている、おそらく隠されたボクシングの状況は他にありますか?


2
私はこれに少し前に対処しましたが、これは非常に興味深いもの
George Duckett 2011年

あなたが示した非常に素晴らしい変換方法。実際、私は2番目のものを知らなかったか、おそらく試したことがありませんでした:)ありがとう
Zenwalker 2011年

12
コミュニティウィキの質問である必要があります
Sly

2
null許容型はどうですか? private int? nullableInteger
アランソン2011年

1
@allansson、NULL可能なタイプは、だけの種類の値のタイプがある
controlflow

回答:


42

それは素晴らしい質問です!

ボクシングは、値型への参照が必要な場合という1つの理由で発生します。あなたがリストしたものはすべてこのルールに該当します。

たとえば、オブジェクトは参照型であるため、値型をオブジェクトにキャストするには、値型への参照が必要です。これにより、ボックス化が発生します。

考えられるすべてのシナリオを一覧表示する場合は、オブジェクトまたはインターフェイスタイプを返すメソッドから値タイプを返すなど、派生物も含める必要があります。これにより、値タイプがオブジェクト/インターフェイスに自動的にキャストされるためです。

ちなみに、あなたが鋭敏に特定した文字列連結のケースも、オブジェクトへのキャストに由来します。+演算子は、コンパイラによって文字列のConcatメソッドの呼び出しに変換されます。このメソッドは、渡された値型のオブジェクトを受け入れるため、オブジェクトへのキャスト、つまりボクシングが発生します。

リストは長くて覚えにくいので、何年もの間、私は常に開発者に、すべてのケースを記憶するのではなく、ボクシングの単一の理由(上記で指定)を覚えておくようにアドバイスしてきました。これにより、コンパイラがC#コード用に生成するILコードの理解も促進されます(たとえば、+ on stringはString.Concatへの呼び出しを生成します)。コンパイラが何を生成するのか疑問がある場合、およびボクシングが発生した場合は、IL逆アセンブラ(ILDASM.exe)を使用できます。通常、ボックスオペコードを探す必要があります(ILにボックスオペコードが含まれていない場合でも、ボクシングが発生する可能性があるケースは1つだけです。詳細については、以下を参照してください)。

しかし、私はいくつかのボクシングの発生があまり明白ではないことに同意します。それらの1つをリストしました:値型のオーバーライドされていないメソッドの呼び出し。実際、これは別の理由であまり明白ではありません。ILコードをチェックすると、ボックスオペコードは表示されませんが、制約オペコードが表示されるため、ILでも、ボクシングが発生することは明らかではありません。この答えがさらに長くなるのを防ぐ理由については、正確には詳しく説明しません...

あまり目立たないボクシングのもう1つのケースは、構造体から基本クラスのメソッドを呼び出す場合です。例:

struct MyValType
{
    public override string ToString()
    {
        return base.ToString();
    }
}

ここではToStringがオーバーライドされるため、MyValTypeでToStringを呼び出してもボクシングは生成されません。ただし、実装はベースToStringを呼び出すため、ボクシングが発生します(ILを確認してください)。

ちなみに、これらの2つの非自明なボクシングのシナリオも、上記の1つのルールから派生しています。値型の基本クラスでメソッドが呼び出されるとき、thisキーワードが参照するものが必要です。値型の基本クラスは(常に)参照型であるため、thisキーワードは参照型を参照する必要があります。したがって、値型への参照が必要であり、単一のルールのためにボックス化が発生します。

ボクシングについて詳しく説明している私のオンライン.NETコースのセクションへの直接リンクは次のとおりです。http//motti.me/mq

より高度なボクシングのシナリオにのみ興味がある場合は、そこに直接リンクがあります(ただし、より基本的なことについて説明すると、上記のリンクからもアクセスできます):http//motti.me/mu

これがお役に立てば幸いです。

モッティ


1
ToString()オーバーライドしない特定の値型でaが呼び出された場合、値型は呼び出しサイトでボックス化されますか、またはメソッドは(ボックス化なしで)自動生成されたオーバーライドにディスパッチされます。ボクシング)を基本メソッドに?
スーパーキャット2014

@supercatbase値型を呼び出すメソッドを呼び出すと、ボクシングが発生します。これには、構造体によってオーバーライドされObjectない仮想メソッドと、まったく仮想ではないメソッド(のようにGetType())が含まれます。この質問を参照してください。
–ŞafakGür 2014

@ŞafakGür:最終結果がボクシングになることはわかっています。私はそれが起こる正確なメカニズムについて疑問に思いました。ILを生成するコンパイラーは、型が値型であるか参照型であるか(ジェネリックである可能性があります)を知らない可能性があるため、関係なくcallvirtを生成します。JITterは、型が値型であるかどうか、およびToStringをオーバーライドするかどうかを認識しているため、ボクシングを行うための呼び出しサイトコードを生成できます。あるいは、ToStringメソッドpublic override void ToString() { return base.ToString(); }をオーバーライドしないすべての構造体に対して自動生成することもできます...
supercat 2014

...そのメソッド内でボクシングを実行します。メソッドは非常に短いので、インライン化することができます。後者のアプローチで物事を行うと、ToString()他の方法と同じようにReflectionを介して構造体のメソッドにアクセスし、構造体タイプをrefパラメーターとして受け取る静的デリゲートを作成するために使用できます[このようなものは継承されていない構造体メソッドで機能します]が、私はそのようなデリゲートを作成しようとしましたが、機能しませんでした。構造体のToString()メソッドの静的デリゲートを作成することは可能ですか?その場合、パラメーターの型はどうあるべきですか?
スーパーキャット2014

リンクが壊れています。
ofirD 2017

5

値型で非仮想GetType()メソッドを呼び出す:

struct S { };
S s = new S();
s.GetType();

2
GetType非仮想であるという理由だけでなく、ヒープオブジェクトとは異なり、値型の格納場所にはGetType()型を識別するために使用できる「非表示」フィールドがないため、ボックス化が必要です。
スーパーキャット2014

@supercatうーん。1.コンパイラによって追加されたボクシングとランタイムによって使用される隠しフィールド。ランタイムについて知っているのでコンパイラがボクシングを追加するかもしれません…2。列挙値で非仮想ToString(string)を呼び出すと、ボクシングも必要になります。コンパイラはEnum.ToString(string)の実装の詳細を知っているので、これを追加するとは思いません。 。つまり、「基本値型」の非仮想メソッドが呼び出されたときに、ボクシングは常に発生したと言えます。
Viacheslav Ivanov 2014

私はと考えられていなかったEnumのに、それ自身のいずれかの非仮想メソッドを持つToString()ための方法は、Enum型情報へのアクセスを持っている必要があります。私はかしらObjectValueTypeまたはEnum型情報なしに自分の仕事を行うことができ、任意の非仮想メソッドを持っています。
スーパーキャット2014

3

Mottiの回答で言及されており、コードサンプルで説明しています。

関連するパラメータ

public void Bla(object obj)
{

}

Bla(valueType)

public void Bla(IBla i) //where IBla is interface
{

}

Bla(valueType)

しかし、これは安全です:

public void Bla<T>(T obj) where T : IBla
{

}

Bla(valueType)

戻り値の型

public object Bla()
{
    return 1;
}

public IBla Bla() //IBla is an interface that 1 inherits
{
    return 1;
}

制約のないTをnullに対してチェックする

public void Bla<T>(T obj)
{
    if (obj == null) //boxes.
}

ダイナミックの使用

dynamic x = 42; (boxes)

もう一つ

enumValue.HasFlag


0
  • またはSystem.Collectionsなどの 非ジェネリックコレクションを使用します。ArrayListHashTable

確かに、これらは最初のケースの特定のインスタンスですが、隠れた落とし穴になる可能性があります。それは代わりにこれらを使用して、私はまだ今日出くわしコードの量驚くべきことだList<T>Dictionary<TKey,TValue>


0

ArrayListに値型の値を追加すると、ボクシングが発生します。

ArrayList items = ...
numbers.Add(1); // boxing to object
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.