11

次のコードについていくつか質問があります。

using System;

namespace ConsoleApplication2
{
    public struct Disposable : IDisposable
    {
        public void Dispose() { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (Test()) { }
        }

        static Disposable Test()
        {
            return new Disposable();
        }
    }
}

私の質問は次のとおりです。

  • ボックスDisposableから返された構造体で動作する using ステートメントは、構造体であるかどうか。Test()
  • どうすれば自分で答えを見つけることができますか?

自分自身を見つけようとするために、上記のコードによって生成された IL を調べました。Main(...)メソッドの IL は次のとおりです。

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] valuetype ConsoleApplication2.Disposable CS$3$0000)
    L_0000: call valuetype ConsoleApplication2.Disposable ConsoleApplication2.Program::Test()
    L_0005: stloc.0 
    L_0006: leave.s L_0016
    L_0008: ldloca.s CS$3$0000
    L_000a: constrained ConsoleApplication2.Disposable
    L_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0015: endfinally 
    L_0016: ret 
    .try L_0006 to L_0008 finally handler L_0008 to L_0016
}

そこにある仮想メソッドの呼び出しがボックス化操作を導入すると思われますL_0010が、実際のbox命令はここにはありません。

私が尋ねている理由は、しばらく前、おそらく 1 ~ 2 年前に、誰かがコメントした using ステートメントの「最適化」をオンラインで見たからです。オブジェクトの短時間ロックの構文として using ステートメントが使用され、メソッドでロックが取得され、構造体が返され、破棄されるとロックが解放される場合がありました。コードは次のようになります。 :

using (LockTheObject())
{
    // use the object
}

コメントは、LockTheObjectメソッドの戻り値の型をIDisposable実際に使用される構造体に変更することで、ボクシングが回避されたというものでした。

しかし、私はこれが本当なのか、それともまだ本当なのか疑問に思っています。

誰かが私を正しい方向に向けることができますか? ボックスの操作を確認するためにランタイム アセンブリ コードを検査する必要がある場合は、何を探すべきかの例を示してください。私はアセンブリ コードに精通しているので問題ありませんが、何も飛び出していません。私もそれを見たとき。

4

3 に答える 3

10

usingステートメントに入れられる値型はボックス化されないように見えます。IDisposableボックス化は、実装する値型がusingステートメント内にあり、他のコンテキストにはない場合にのみ省略されるため、これはC#最適化のように見えます。

詳細については、使用ステートメントと使い捨て値タイプを参照してください。

少し前に、Ian Griffithsは、TimedLockクラスの改善について書きました。このクラスでは、クラスから構造体に変更されました。この変更により、IDisposableを実装する値型が作成されました。すぐに忘れてしまった時、頭の中でしつこい質問がありました。問題は、Disposeを呼び出すときに、そのタイプのインスタンスがボックス化されないかどうかです。

そしてまた、ああ、いや!TimedLockではありません!

lockJohn Sandsは、最近のブログで、C#のキーワードの便利さのほとんどを放棄せずにロックのタイムアウトを使用するためのコードの欠陥を指摘しています。

于 2009-08-25T20:00:18.730 に答える
7

これはIf my struct implements IDisposable will it be boxed when used in a using statement?の複製です。

更新: この質問は、2011 年 3 月の私のブログの主題でした。素晴らしい質問をありがとう!

Andrew Hare の答えは正しいです。興味深い追加のメモを追加したかっただけです。私たちが実行する最適化 -- 制約のある callvirt を使用して、可能な場合はボックス化をスキップする -- は、実際には、厳密に言えば C# 仕様に違反しています。仕様では、値型リソースに対して生成する最終ブロックは次のようになっています。

     finally 
     {
         ((IDisposable)resource).Dispose();
     }

これは明らかに値型のボクシング変換です。実装にボックス化の欠如が見られる不自然なシナリオを構築することは可能です。

(この仕様違反を指摘してくれた Vladimir Reshetnikov に感謝します。)

于 2009-08-25T20:19:04.493 に答える
4

値型のthisインスタンス メソッドは、参照型のインスタンス メソッドと同様に、パラメーターを最初の引数として受け取ります。ただし、この場合のパラメーターは、ボックス化されたオブジェクトへの参照ではなく、オブジェクトのデータへのマネージ ポインターです。次のようにメモリに配置されていることがわかります。

Unboxed object:
-----------------------------------------
|              DATA                     |
-----------------------------------------
 ^ managed pointer to struct

Boxed object:
------------------------------------------------------------
| GC/Object header |              [Boxed] DATA             |
------------------------------------------------------------
                    ^ The 'unbox' opcode gives a managed pointer to the boxed data
 ^ A *reference* to any instance of a reference type or boxed object, points here

DATAこれらの場合の両方で同じです¹。

値型のインスタンス メソッドは、特にデータへのマネージ ポインターを想定しているため、オブジェクトをボックス化する必要はありません。上記のように、constrainedオペコードは呼び出しの前に使用されます。これは、次の命令が通常受け取るオブジェクト参照ではなく、構造体callvirtへのマネージ ポインターを受け取っていることをランタイムに伝えます。ConsoleApplication2.Disposableそうすることで、JIT はDispose()構造体によって実装された封印されたオーバーロードを解決し、オブジェクトをボックス化せずに直接呼び出すことができます。プレフィックスがなければconstrained、命令に渡されるオブジェクトはオブジェクト参照でなければなりません。これは、標準の仮想呼び出しの動的解決手順が GC/Object ヘッダーが常にcallvirt存在するという事実に基づいているためです。期待される場所に - はい、これは値型のボックス化を強制します。

¹ 今は無視して先に進みますNullable<T>

于 2009-08-25T20:08:45.950 に答える