1. ホーム
  2. c#

[解決済み] C#のボクシング・オカレンス

2023-05-05 07:34:04

質問

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# コンパイラ 連結を最適化する を含む bool , char , IntPtr , UIntPtr タイプ

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

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

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

    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で最適化されることがあります。

箱詰めの状況は、もしかしたら隠れているかもしれませんが、他にもありますか?

どのように解決するのですか?

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

ボクシングが発生する理由はまさに1つです。 . あなたが挙げたものはすべて、このルールに該当します。

例えばobjectは参照型なので、value型をobjectにキャストするにはvalue型への参照が必要となり、ボクシングが発生します。

すべての可能なシナリオをリストアップしたいのであれば、object または interface 型を返すメソッドから値型を返すといった派生的なものも含める必要があります。

ところで、あなたが鋭く指摘した文字列の連結のケースも、オブジェクトへのキャストから派生しています。演算子はコンパイラーによって文字列の Concat メソッドの呼び出しに変換され、渡された値の型にオブジェクトを受け入れるので、オブジェクトへのキャストとそれによる箱詰めが発生します。

長年にわたり、私は常に開発者に、ケースが長く覚えるのが大変なので、すべてのケースを記憶するのではなく、(上記で指定した)ボクシングの単一の理由を記憶するように助言してきました。これは、コンパイラが C# コードに対して生成する IL コード (たとえば、+ on string は String.Concat の呼び出しを生成する) の理解を促進することにもなります。コンパイラがどのようなコードを生成するか、また、ボックス化が発生するかどうかが不明な場合は、ILディスアセンブラ(ILDASM.exe)を使用することができます。通常、ボックス オペコードを探します(ただし、IL にボックス オペコードが含まれていないにもかかわらず、ボックス化が発生する場合があります。)

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

構造体から基底クラスのメソッドを呼び出す場合にも、ボクシングは発生しません。例を挙げます。

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

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

ところで、これら2つの明白でない箱詰めのシナリオも、上記の1つのルールから派生しています。メソッドが値型の基底クラスで呼び出されるとき、何か この キーワードが参照するものがなければなりません。値型の基底クラスは(常に)参照型であるため、このキーワードは この キーワードは参照型を参照しなければならないので、値型への参照が必要となり、単一ルールにより箱詰めが発生します。

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

もしあなたがより高度なボクシングのシナリオにしか興味がないのであれば、ここに直接リンクがあります(ただし、上のリンクはより基本的なものを論じているので、そこへも行くことができます)。 http://motti.me/mu

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

モッティ