[TestFixture]
public class Tests
{
private class Relay
{
public Action Do { get; set; }
}
[Test]
public void OptimizerStrangeness()
{
var relay = new Relay();
var indicator = 0;
relay.Do = () => indicator++;
var weak = new WeakReference(relay);
GC.Collect();
var relayNew = weak.Target as Relay;
if (relayNew == null) Assert.Fail();
relayNew.Do();
Assert.AreEqual(1, indicator);
}
}
このコードは、変数がまだスコープ内にあり、インスタンスへの強い参照があるにAssert.Fail()
もかかわらず、行のリリースモードでのみ失敗するため、WeakReferenceはまだ死んではなりません。relay
UPD:少し明確にするために:私はそれが「最適化」できることを理解しています。indicator
しかし、この最適化変数に応じて、0
または1
値があります。つまり、実際に目に見える動作の変化があります。
UPD2:C#言語仕様、セクション3.9から
オブジェクトまたはその一部に、デストラクタの実行以外の実行の継続によってアクセスできない場合、オブジェクトは使用されなくなったと見なされ、破棄の対象になります。C#コンパイラとガベージコレクタは、コードを分析して、オブジェクトへのどの参照が将来使用される可能性があるかを判断することを選択する場合があります。たとえば、スコープ内にあるローカル変数がオブジェクトへの唯一の既存の参照であるが、そのローカル変数がプロシージャ内の現在の実行ポイントからの実行の可能な継続で参照されない場合、ガベージコレクタは可能性があります(ただし、 )オブジェクトを使用されなくなったものとして扱う必要はありません。
技術的に言えば、このオブジェクトは実行を継続することでアクセスでき、アクセスできるため、「使用されなくなった」として扱うことはできません(実際、C#仕様は、コンパイラではなくCLRの側面であるため、弱参照については何も述べていません-コンパイラ出力結構です)。CLR/JITに関するメモリ管理情報を検索しようとします。
UPD3:CLRのメモリ管理に関する情報は次のとおりです-セクション「メモリの解放」:
...すべてのアプリケーションには一連のルートがあります。各ルートは、マネージヒープ上のオブジェクトを参照するか、nullに設定されます。アプリケーションのルートには、グローバルおよび静的オブジェクトポインター、スレッドのスタック上のローカル変数と参照オブジェクトパラメーター、およびCPUレジスタが含まれます。ガベージコレクターは、ジャストインタイム(JIT)コンパイラーとランタイムが維持するアクティブなルートのリストにアクセスできます。このリストを使用して、アプリケーションのルートを調べ、その過程で、ルートから到達可能なすべてのオブジェクトを含むグラフを作成します。
問題の変数は間違いなくローカル変数であるため、到達可能です。このように、この言及は非常に迅速で曖昧なので、より具体的な情報を見て本当にうれしいです。
UPD4:.NET Frameworkのソースから:
// This method DOES NOT DO ANYTHING in and of itself. It's used to
// prevent a finalizable object from losing any outstanding references
// a touch too early. The JIT is very aggressive about keeping an
// object's lifetime to as small a window as possible, to the point
// where a 'this' pointer isn't considered live in an instance method
// unless you read a value from the instance. So for finalizable
// objects that store a handle or pointer and provide a finalizer that
// cleans them up, this can cause subtle ----s with the finalizer
// thread. This isn't just about handles - it can happen with just
// about any finalizable resource.
//
// Users should insert a call to this method near the end of a
// method where they must keep an object alive for the duration of that
// method, up until this method is called. Here is an example:
//
// "...all you really need is one object with a Finalize method, and a
// second object with a Close/Dispose/Done method. Such as the following
// contrived example:
//
// class Foo {
// Stream stream = ...;
// protected void Finalize() { stream.Close(); }
// void Problem() { stream.MethodThatSpansGCs(); }
// static void Main() { new Foo().Problem(); }
// }
//
//
// In this code, Foo will be finalized in the middle of
// stream.MethodThatSpansGCs, thus closing a stream still in use."
//
// If we insert a call to GC.KeepAlive(this) at the end of Problem(), then
// Foo doesn't get finalized and the stream says open.
[System.Security.SecuritySafeCritical] // auto-generated
[ResourceExposure(ResourceScope.None)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
public static extern void KeepAlive(Object obj);
興味があれば、詳細についてはこちらをご覧ください。