Click here to Skip to main content
15,851,090 members
Articles / Programming Languages / C#
Article

Object-Oriented Static Destructors

Rate me:
Please Sign up or sign in to vote.
3.60/5 (9 votes)
28 Jul 2006CPOL5 min read 58.5K   14   17
In Delphi, there was always available initialization and finalization section for units. But have you ever wondered how to achieve the same using .NET?

Introduction

I was reviewing some Delphi code and, while doing so, I noticed an old "friend" of mine: the initialization and finalization sections of units. Not all units bear them, but they represent the first and last blocks of code to be executed for that unit. And this can be verified if you debug an application step by step: all initialization sections are executed before any other line on the main procedure is executed.

Just as I watched those lines of code, I started to wonder: I can replace an initialization with some static constructor, with the advantage that it'll only execute if I need that class. But how about the finalization section?

Instance Destructors

For those used to C#, a destructor can be declared in two ways:

C#
class MyClass {

   // this way...
   ~MyClass() {
      // Destruction code goes here
   }

   // or this...
   protected override void Finalize() {
      try {
         // Destruction code goes here
      } finally {
        base.Finalize();
      }
   }
}

In the end, either way will translate into the same IL, but you cannot have both on the same class. I'm not very familiar with VB.NET, but only one version exists here:

VB.NET
Class MyClass

   Overrides Protected Sub Finalize()
      ' Destruction code goes here
   End Sub

End Class

This will have the same effect as the C# codes and, in both languages, enables you to finish open (or forgotten open) connections, record logs of an object's destruction and so forth. But what do you do when you want some behavior when an application is being terminated?

Static Constructors

There is no straight way to provide the same for the entire class. But as I gave thought to the matter, I came up with the smallest possible code to achieve it. This approach goes through a static constructor. Why? Because it is granted to be executed only once (if it's not, I believe it's a bug) the first time a class is referenced, no matter for which purpose. This is also an excellent point to create singletons for the same reason (isn't that what we want with a singleton?).

At this point, if you're very familiar with the singleton pattern, you might already have a fair idea of how to create the static destructor.

If you didn't guess, here is the answer: since a singleton is an instance of an object and instances can bear Finalize methods, all we have to do is to instantiate a singleton class (perhaps private) which will do the job we want. That class is instantiated in the static constructor. Take a look:

C#
class MyClass {

   // This goes automatically inside the static constructor
   private static readonly StaticDestructor sd = new StaticDestructor();

   private class StaticDestructor {
      ~StaticDestructor() {
         // Static destruction code goes here
      }
   }
}

There is no need to be manipulating such an object. For as long as the reference exists, the object will not be disposed by the CG (so I expect; it's meant to be this way). When the application terminates, any objects pending finalization will be disposed naturally, thus allowing our pseudo static destructor to be called.

Although, it's important to notice that, just as static constructors, static destructors are meant to deal with static elements. Despite the fact that the StaticDestructor class has an instance, it doesn't belong to any particular instance of MyClass.

Unloading Order

As spotted out by carlopagliei, this approach does not consider the fact that, in Delphi, finalization sections are executed in reverse order of initialization sections. But how could this be done in an object oriented way?

Not with our previous approach, for sure. The need to recall the order in which classes have been "initialized", if you need to reverse its actions in a precise order, renders it useless. The only managed way to do this is to register every finalization section we need to be called. This will demand an auxiliary class and the use of some reflection.

C#
public sealed class CodeManager {

   private static List<MethodInfo> actions = new List<MethodInfo>();
   private static CodeManager singleton = new CodeManager();
   private static List<Type> types = new List<Type>();

   private CodeManager() { }

   ~CodeManager() {
      foreach(MethodInfo mi in actions)
         mi.Invoke(null, null);
   }

   public static void Register(Type type) {
      // Initialized types must be ignored...
      if(types.Contains(type)) return;
      types.Add(type);
      // Initializations must be done now...
      MethodInfo mi = type.GetMethod("Initialization", BindingFlags.Static |
         BindingFlags.Public | BindingFlags.NonPublic, null, Types.EmptyType, null);
      if(mi != null) mi.Invoke(null, null);
      // Finalizations must be performed backwards...
      mi = type.GetMethod("Finalization", BindingFlags.Static |
         BindingFlags.Public | BindingFlags.NonPublic, null, Types.EmptyType, null);
      if(mi != null) actions.Insert(0, mi);
   }
}

This class is a little bit more complex, but analyzing its code, you will notice it'll demand you to register a class so it can be finalized, but this behavior is not needed for initializations. It's best if you perform initializations inside the static constructor, to ensure its code won't be executed twice.

You will need one or two methods declared static and have no return value or extra arguments to be called. Declaring them as public is optional, but if you obfuscate the assembly, you may lose their reference by name. They should be declared as follows:

C#
public static void Initialization() { ... }

public static void Finalization() { ... }

However, there is a flaw in this approach as well, as the initialization methods are exposed and become prone to a second call. The same is valid for the finalization section. It's important to remember that code inside these methods are meant to be executed only once during the application's lifetime. So, using the static constructor whenever possible can be considered a good practice.

Points of Interest

I have actually found no need for a static destructor myself, but you can use it virtually anywhere. Some possible uses might be:

  • Centralized logging: you could dispose of the log service appropriately
  • Special object control: some instances should be coordinated during disposal
  • Global connection with a server: all data comes through a single coordinated connection (this can be done with a simple singleton and it's own destructor, but you're the developer)

Some optimization tools might understand that there is no use for the object in the first approach or its class and simply remove them from IL. There are only two ways to contour this: not to optimize, or to inject some code into the destructor which is used (even vaguely) by the main class. This method could be a simple sum of two numbers or some encryption routine, but it's important not to be empty. If it's referenced in two or more methods in the main class, the better. For the second approach, declaring the methods private or internal may cause removal of their name, disabling reflection.

Also, I haven't tried to access instance members inside the static destructor in the first approach, but I believe it's not possible once the inner class instance is held static. Surely you can access the static destructor class from instances, that's the reason why I mark the static field read-only.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Brazil Brazil
architecture student (5/10)​ · designer · developer · geek

Comments and Discussions

 
GeneralOne need for a static destructor Pin
ke4vtw10-Oct-07 7:53
ke4vtw10-Oct-07 7:53 
AnswerRe: One need for a static destructor Pin
Leonardo Pessoa10-Oct-07 15:39
Leonardo Pessoa10-Oct-07 15:39 
GeneralRe: One need for a static destructor [modified] Pin
Jon Person13-Aug-08 12:10
Jon Person13-Aug-08 12:10 
GeneralRe: One need for a static destructor Pin
Southmountain2-Apr-22 9:25
Southmountain2-Apr-22 9:25 
GeneralRemark Pin
wout de zeeuw29-Jul-06 0:46
wout de zeeuw29-Jul-06 0:46 
AnswerRe: Remark Pin
Leonardo Pessoa29-Jul-06 7:59
Leonardo Pessoa29-Jul-06 7:59 
GeneralRe: Remark Pin
_Artist_1-Aug-06 3:20
_Artist_1-Aug-06 3:20 
GeneralRe: Remark Pin
Leonardo Pessoa2-Aug-06 3:40
Leonardo Pessoa2-Aug-06 3:40 
GeneralRe: Remark Pin
_Artist_2-Aug-06 4:04
_Artist_2-Aug-06 4:04 
GeneralRe: Remark Pin
Leonardo Pessoa2-Aug-06 4:54
Leonardo Pessoa2-Aug-06 4:54 
Generalwell but... Pin
carlop()28-Jul-06 7:46
carlop()28-Jul-06 7:46 
AnswerRe: well but... Pin
Leonardo Pessoa28-Jul-06 7:55
Leonardo Pessoa28-Jul-06 7:55 
Thanks for your feedback. In fact, you can't control the order of these class destructors, as you can't control the order of execution for class constructors.

As answer to your concern, I'm updating the article to consider a second option: a singleton to register disposing calls to be called in reverse order. And this will also be it's biggest problem: remember to register the finalizer.

Check back later and see if these new approaches fit you better.

[]'s
Harkos
---
"Money isn't our god, integrity will free our soul."
Cut Throat - Sepultura

GeneralRe: well but... Pin
sadavoya3-Aug-06 8:12
sadavoya3-Aug-06 8:12 
GeneralRe: well but... Pin
Leonardo Pessoa3-Aug-06 15:29
Leonardo Pessoa3-Aug-06 15:29 
GeneralRe: well but... Pin
sadavoya4-Aug-06 5:45
sadavoya4-Aug-06 5:45 
GeneralRe: well but... Pin
Leonardo Pessoa4-Aug-06 15:43
Leonardo Pessoa4-Aug-06 15:43 
GeneralRe: well but... Pin
sadavoya5-Aug-06 14:31
sadavoya5-Aug-06 14:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.