web-dev-qa-db-ja.com

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

私はボクシングが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/is演算子を使用した、制約なしまたはasジェネリック型の型テスト値:

    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によって最適化される場合があります

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

83
controlflow

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

ボクシングが発生する理由は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/m

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

モッティ

41
Motti Shaked

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

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

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)

もう1つ

enumValue.HasFlag

3
nawfal
  • ArrayListHashTableなどのSystem.Collectionsの非ジェネリックコレクションを使用する。

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

0
Jesse C. Slicer

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

ArrayList items = ...
numbers.Add(1); // boxing to object
0
sll