using System;
using System.Threading;
public class Example
{
// The N property is very fast, because all it does is return
// a stored integer. Therefore the finalizer almost never runs
// before this property is read.
//
private int nValue;
public int N { get { return nValue; }}
// The Hash property is slower because it clones an array. When
// KeepAlive is not used, the finalizer sometimes runs before
// the Hash property value is read.
//
private byte[] hashValue;
public byte[] Hash { get { return (byte[]) hashValue.Clone(); }}
// The constructor initializes the property values.
//
public Example()
{
nValue = 2;
hashValue = new byte[20];
hashValue[0] = 2;
}
// The finalizer sets the N property to zero, and clears all
// the elements of the array for the Hash property. The finalizer
// runs on a separate thread provided by the system. In some
// cases, the finalizer can run while a member of the Example
// instance is still executing.
//
~Example()
{
nValue = 0;
if (hashValue != null)
{
Array.Clear(hashValue, 0, hashValue.Length);
}
}
}
public class Test
{
private static int totalCount = 0;
private static int finalizerFirstCount = 0;
// This variable controls the thread that runs the demo.
private static bool running = true;
// The default is to run without KeepAlive.
private static TestKind kind = TestKind.NoKeepAlive;
// See the comment at the end of the SafeDoWork method.
//private static bool keepAlive = false;
// In order to demonstrate the finalizer running first, the
// DoWork method must create an Example object and invoke its
// Hash property. If there are no other calls to members of
// the Example object in DoWork, garbage collection reclaims
// the Example object aggressively. Sometimes this means that
// the finalizer runs before the call to the Hash property
// completes.
private static void DoWork()
{
totalCount++;
// Create an Example object and save the value of the
// Hash property. There are no more calls to members of
// the object in the DoWork method, so it is available
// for aggressive garbage collection.
//
Example ex = new Example();
byte[] res = ex.Hash;
// If the finalizer runs before the call to the Hash
// property completes, the hashValue array might be
// cleared before the property value is read. The
// following test detects that.
//
if (res[0] != 2)
{
finalizerFirstCount++;
Console.WriteLine("The finalizer ran first at {0} iterations.",
totalCount);
}
}
// In the SafeDoWork method the finalizer never runs first,
// because of the KeepAlive at the end of the method.
//
private static void SafeDoWork()
{
totalCount++;
// Create an Example object and save the value of the
// Hash property.
Example ex = new Example();
byte[] res = ex.Hash;
// The finalizer cannot run before the property is read,
// because the KeepAlive method prevents the Example object
// from being reclaimed by garbage collection.
//
if (res[0] != 2)
{
finalizerFirstCount++;
Console.WriteLine("The finalizer ran first at {0} iterations.",
totalCount);
}
GC.KeepAlive(ex);
// The KeepAlive method need never be executed. For example,
// if the keepAlive field is uncommented, the following line
// of code prevents the finalizer from running first, even
// though it is impossible for the KeepAlive method ever to
// be executed.
// if (keepAlive) GC.KeepAlive(ex);
// However, if the compiler detects that the KeepAlive can
// never be executed, as in the following line, then it will
// not prevent the finalizer from running first.
// if (false) GC.KeepAlive(ex);
}
// In the TrivialDoWork method the finalizer almost never runs
// first, even without a KeepAlive at the end of the method,
// because accessing the N property is so fast.
//
private static void TrivialDoWork()
{
totalCount++;
// Create an Example object and save the value of the
// N property.
Example ex = new Example();
int res = ex.N;
// The finalizer almost never runs before the property is read,
// because accessing the N property is so fast.
//
if (res != 2)
{
finalizerFirstCount++;
Console.WriteLine("The finalizer ran first at {0} iterations.",
totalCount);
}
}
public static void Main (string[] args)
{
if (args.Length != 0)
{
string arg = args[0].ToLower();
if (arg.Length < 10 && arg == "keepalive".Substring(0, arg.Length))
kind = TestKind.KeepAlive;
if (arg.Length < 8 && arg == "trivial".Substring(0, arg.Length))
kind = TestKind.Trivial;
}
Console.WriteLine("Test: {0}", kind);
// Create a thread to run the test.
Thread t = new Thread(new ThreadStart(ThreadProc));
t.Start();
// The thread runs until Enter is pressed.
Console.WriteLine("Press Enter to stop the program.");
Console.ReadLine();
running = false;
// Wait for the thread to end.
t.Join();
Console.WriteLine("{0} iterations total; the finalizer ran first {1} times.",
totalCount, finalizerFirstCount);
}
private static void ThreadProc()
{
switch (kind)
{
case TestKind.KeepAlive:
while (running) SafeDoWork();
break;
case TestKind.Trivial:
while (running) TrivialDoWork();
break;
default:
while (running) DoWork();
break;
}
}
private enum TestKind
{
NoKeepAlive,
KeepAlive,
Trivial
}
}
/* When run with the default NoKeepAlive test, on a dual-processor
computer, this example produces output similar to the following:
Test: NoKeepAlive
Press Enter to stop the program.
The finalizer ran first at 21098618 iterations.
The finalizer ran first at 33944444 iterations.
The finalizer ran first at 35160207 iterations.
53169451 iterations total; the finalizer ran first 3 times.
*/