9

ReflectorでDLLの逆コンパイルされたソースコードを参照していたところ、次のC#コードに出くわしました。

protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool flag1)
{
    if (flag1)
    {
        this.~ClassName();
    }
    else
    {
        base.Finalize();
    }
}

私の最初の反応は「なに?ファイナライザーを手動で呼び出せないと思った!」でした。

注:基本タイプはobjectです。

それがリフレクターの癖ではないことを確認するために、私はILSpyでメソッドを開きました。同様のコードを生成しました。

私は新しい発見を確認するためにGoogleに行きました。私はのドキュメントを見つけましたObject.Finalize、そしてこれはそれが言ったことです:

派生型でのFinalizeのすべての実装は、その基本型のFinalizeの実装を呼び出す必要があります。これは、アプリケーションコードがFinalizeを呼び出すことができる唯一のケースです。

今、私は何を考えるべきかわかりません。DLLがC++でコンパイルされたことが原因である可能性があります。(注:Disposeの実装が見つかりませんでした。自動生成されている可能性があります。)これは、IDisposable.Disposeメソッドの特別な許容値である可能性があります。両方の逆コンパイラの欠陥である可能性があります。

いくつかの観察:

  • ソースコードにDisposeの実装が見つかりませんでした。多分それは自動生成されます。
  • Reflectorは、という名前のメソッドを示してい~ClassNameます。このメソッドは実際にはファイナライザーではなく、C ++デストラクタ、または通常のメソッドであるように見えます。

これは合法的なC#ですか?もしそうなら、この場合の違いは何ですか?そうでない場合、実際に何が起こっていますか?C ++ / CLIでは許可されていますが、C#では許可されていませんか?それとも、逆コンパイラの不具合ですか?

4

2 に答える 2

5

他の回答者が指摘しているように、あなたは正しいです。廃棄コードが異なる理由は、C ++/CLIだからです。

C ++ / CLIは、クリーンアップコードの記述に別のイディオムを使用します。

  • C#:Dispose()と〜ClassName()(ファイナライザー)はどちらもDispose(bool)を呼び出します。
    • 3つのメソッドはすべて、開発者によって作成されています。
  • C ++ / CLI:Dispose()とFinalize()はどちらもDispose(bool)を呼び出し、Dispose(bool)は〜ClassName()または!ClassName()(それぞれデストラクタとファイナライザー)を呼び出します。
    • 〜ClassName()と!ClassName()は開発者によって書かれています。
      • お気づきのとおり、〜ClassName()はC#とは異なる方法で処理されます。C ++ / CLIでは、「〜ClassName」という名前のメソッドとして残りますが、C#の〜ClassName()はとしてコンパイルされprotected override void Finalize()ます。
    • Dispose()、Finalize()、およびDispose(bool)は、コンパイラーによってのみ記述されます。そうすることで、コンパイラーは通常は想定されていないことを実行します。

実例を示すために、単純なC ++/CLIクラスを次に示します。

public ref class TestClass
{
    ~TestClass() { Debug::WriteLine("Disposed"); }
    !TestClass() { Debug::WriteLine("Finalized"); }
};

これがReflectorからの出力で、C#構文に逆コンパイルされています。

public class TestClass : IDisposable
{
    private void !TestClass() { Debug.WriteLine("Finalized"); }
    private void ~TestClass() { Debug.WriteLine("Disposed"); }

    public sealed override void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    [HandleProcessCorruptedStateExceptions]
    protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool disposing)
    {
        if (disposing)
        {
            this.~TestClass();
        }
        else
        {
            try
            {
                this.!TestClass();
            }
            finally
            {
                base.Finalize();
            }
        }
    }

    protected override void Finalize()
    {
        this.Dispose(false);
    }
}

編集

C ++ / CLIはC#よりもコンストラクターの例外をうまく処理しているようです。

C ++ / CLIとC#の両方でテストアプリを作成しました。これらは、ParentクラスとChildクラスを定義し、Childクラスのコンストラクターが例外をスローします。どちらのクラスにも、コンストラクター、disposeメソッド、およびファイナライザーからのデバッグ出力があります。

C ++ / CLIでは、コンパイラーは子コンストラクターの内容をtry / faultブロックにラップし、親のDisposeメソッドをfaultで呼び出します。(フォールトコードは、例外が他のtry / catchブロックによってキャッチされたときに実行されると思います。これは、スタックを上に移動する前にすぐに実行されるcatchまたはfinallyブロックとは対照的です。しかし、そこには微妙な点が欠けている可能性があります。 。)C#では、暗黙のcatchまたはfaultブロックがないため、Parent.Dispose()が呼び出されることはありません。GCがオブジェクトの収集に取り掛かると、両方の言語が子と親の両方のファイナライザーを呼び出します。

これが私がC++/CLIでコンパイルしたテストアプリです:

public ref class Parent
{
public:
    Parent() { Debug::WriteLine("Parent()"); }
    ~Parent() { Debug::WriteLine("~Parent()"); }
    !Parent() { Debug::WriteLine("!Parent()"); }
};

public ref class Child : public Parent
{
public:
    Child() { Debug::WriteLine("Child()"); throw gcnew Exception(); }
    ~Child() { Debug::WriteLine("~Child()"); }
    !Child() { Debug::WriteLine("!Child()"); }
};

try
{
    Object^ o = gcnew Child();
}
catch(Exception^ e)
{
    Debug::WriteLine("Exception Caught");
    Debug::WriteLine("GC::Collect()");
    GC::Collect();
    Debug::WriteLine("GC::WaitForPendingFinalizers()");
    GC::WaitForPendingFinalizers();
    Debug::WriteLine("GC::Collect()");
    GC::Collect();
}

出力:

親()
子()
タイプ「System.Exception」の最初のチャンスの例外がCppCLI-DisposeTest.exeで発生しました
〜Parent()
例外が発生しました
GC :: Collect()
GC :: WaitForPendingFinalizers()
!子()
!親()
GC :: Collect()

Reflectorの出力を見ると、C ++ / CLIコンパイラがChildコンストラクタをコンパイルした方法(C#構文に逆コンパイル)があります。

public Child()
{
    try
    {
        Debug.WriteLine("Child()");
        throw new Exception();
    }
    fault
    {
        base.Dispose(true);
    }
}

比較のために、C#の同等のプログラムを次に示します。

public class Parent : IDisposable
{
    public Parent() { Debug.WriteLine("Parent()"); }
    public virtual void Dispose() { Debug.WriteLine("Parent.Dispose()"); }
    ~Parent() { Debug.WriteLine("~Parent()"); }
}

public class Child : Parent
{
    public Child() { Debug.WriteLine("Child()"); throw new Exception(); }
    public override void Dispose() { Debug.WriteLine("Child.Dispose()"); }
    ~Child() { Debug.WriteLine("~Child()"); }
}

try
{
    Object o = new Child();
}
catch (Exception e)
{
    Debug.WriteLine("Exception Caught");
    Debug.WriteLine("GC::Collect()");
    GC.Collect();
    Debug.WriteLine("GC::WaitForPendingFinalizers()");
    GC.WaitForPendingFinalizers();
    Debug.WriteLine("GC::Collect()");
    GC.Collect();
    return;
}

そしてC#出力:

親()
子()
CSharp-DisposeTest.exeでタイプ「System.Exception」の最初のチャンスの例外が発生しました
例外が発生しました
GC :: Collect()
GC :: WaitForPendingFinalizers()
〜Child()
〜Parent()
GC :: Collect()
于 2012-08-21T00:45:48.150 に答える
1

はい、C ++/CLIコードを見ています。一般的なC++/ CLIパターンであるファイナライザーへの明示的な呼び出しから少し離れると、引数の[MarshalAs]属性は完全な景品です。

C ++ / CLIはC#とは動作が異なり、IDisposableインターフェイスと廃棄パターンは完全に言語に組み込まれています。インターフェイス名を指定することはありません。また、Disposeを直接使用することもできません。非常に典型的な例は、アンマネージC++クラスをラップするrefクラスラッパーです。これをC++/ CLIクラスライブラリに貼り付けて、次のコードから取得したILを確認できます。

using namespace System;

#pragma managed(push, off)
class Example {};
#pragma managed(pop)

public ref class Wrapper {
private:
    Example* native;
public:
    Wrapper() : native(new Example) {}
    ~Wrapper() { this->!Wrapper(); }
    !Wrapper() { delete native; native = nullptr; }
};

「例」はネイティブクラスであり、ラッパーはそのクラスへのポインターをプライベートメンバーとして格納します。コンストラクターは、 new演算子を使用してインスタンスを作成します。これはネイティブの新しい演算子であり、管理対象の演算子はgcnewと呼ばれます。〜Wrapper()メソッドは「デストラクタ」を宣言します。これは実際にはdisposeメソッドです。コンパイラーは、保護されたDispose(bool)メンバーの2つのメンバーを生成します。これは、スニペットで見ているもので、使い捨てパターンの実装としておそらくおなじみのものです。また、Dispose()メソッドも表示されます。C#プログラムで明示的に行うのと同じように、GC.SuppressFinalize()を自動的に呼び出すことに注意してください。

!Wrapper()メンバーはファイナライザーであり、C#デストラクタと同じです。デストラクタから呼び出すことは許可されており、多くの場合、理にかなっています。この例ではそうです。

于 2012-08-21T00:50:41.597 に答える