1

I have several objects in our framework, that by the requirement need to provide event ObjectTerminated. The user of the framework can subscribe to this event and clean-up some unmanaged stuff, that he is using. These objects are designed to exist the whole life time of the application, and I'm not controlling their life. You can think of them as an array of singletons.

I want to write code like this:

class SomeWorkflowControlObject
{
     public event EventHandler<> ObjectTerminated;

     ~SomeWorkflowControlObject()
     {
          if (ObjectTerminated != null) ObjectTerminated(this, null);         
     }
}

I am not sure, am I allowed to do that. What could go possibly wrong with such solution?

Updated:

What about Process.GetCurrentProcess().Exited ? Can I use it in such manner?

Archeg
  • 8,364
  • 7
  • 43
  • 90
  • 1
    This is not a destructor, it is a garbage collection *finalizer*. – stakx - no longer contributing Jul 06 '12 at 12:44
  • 1
    No, finalizer is called from the destrucor. http://msdn.microsoft.com/en-us/library/66x5fx1b(v=vs.80).aspx – Archeg Jul 06 '12 at 12:46
  • This doesn't matter. Unlike VB.NET, C# does not allow you to override `Finalize` directly, and instead forces you to do finalization through something officially called a "destructor"... but calling it "destructor" was (IMO) a very unfortunate decision. It's really the C# version of `object.Finalize`. – stakx - no longer contributing Jul 06 '12 at 12:50
  • @stakx - Very wrong. The `~()` member is officially called a destructor in C#. It maps to the Finalize method. It has nothing to do with GC. – H H Jul 06 '12 at 13:39
  • @Henk: I know what it's officially called. See my comment above. C# destructors *are* `Finalize`, and they have everything to do with GC. [From the documentation of C# destructors on MSDN](http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx): _"The programmer has no control over when the destructor is called because this is determined by the garbage collector."_ The existence of a method named `GC.SuppressFinalize` is further proof of that. – stakx - no longer contributing Jul 06 '12 at 14:15

2 Answers2

1

You should not do this. Basically, destructors do not exist in C#. What you have written is a finalizer, and the only thing a finalizer should ever do is to free unmanaged resources.

You are not allowed to access any other managed object at all, since the garbage collector might already have removed it. I do not think your null check is a sufficient guard against this situation; that object reference might still point to your (event) delegate, even if it's already gone.

So in short, don't do this.

Alternatives:

  1. Subscribe to the Application.ApplicationExit event if you have a Windows Forms application.

  2. You might want to consider implementing the IDisposable interface instead and then do something like this:

    public class SomethingVeryLongLived : IDisposable
    {
        …
    }
    
    …
    
    public static void Main()
    {
        using (var sth = new SomethingVeryLongLived(…))
        {
            Application.Run(new SomeForm(…));
        } // <-- at this point, foo.Dispose() is guaranteed to be called.
    }
    

    Take note that even in the case of using IDisposable, it's probably not a good idea / design to trigger an event inside the object that is getting disposed, since disposed objects should no longer be accessed.

    For this very reason, I would recommend you do the following:

  3. Use a try…finally block:

    public static void Main()
    {
        var sth = new SomethingVeryLongLived(…);
        try
        {
            Application.Run(new SomeForm(…));
        }
        finally
        {
            SomethingVeryLongLived.Terminate();
        }
    }
    

    This would seem best to me because you are not abusing the IDisposable interface, and it's very clear what the code does... there's no hidden meaning.

Community
  • 1
  • 1
stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • I need to have some mechanics to be called before the application is closed. But I cannot use any UI stuff, as we are writing the framework, that doesn't know where it will be used. Dispose won't help, as I am not able to call it – Archeg Jul 06 '12 at 12:49
  • The second option may suite us, but in that case I will force the developer who uses my framework to always writing such code. It's not very good. The first option is bad because I do not know whether the developer will use WinForms or WPF or something else. – Archeg Jul 06 '12 at 13:01
  • @Henk: No. Unmanaged resources are supposed to be dealt with the `IDisposable` pattern, and finalizers are only part of this pattern as a safety net, in case that you forget to explicitly call `Dispose` -- in that case, the garbage collection guarantees an *eventual* freeing of unmanaged resources. I'm happy to discuss this further in chat. – stakx - no longer contributing Jul 06 '12 at 14:07
  • But where does "only thing ... should ever do" come from? And unmanaged resources should be wrapped in a SafeHandle. – H H Jul 06 '12 at 14:32
  • @Henk: I agree with `SafeHandle` (but kept it out of this discussion in order to not stray too far from the question.) -- Essentially, finalizers (aka C# destructors) are called by the GC. Since the GC order in which objects are reclaimed is indeterminate, you cannot assume that any .NET objects ("managed resources") that your object has a reference to are still alive, and thus you should not try to access them at all. All you're allowed to do is to access the present object's own data, such as unmanaged resource handles (perhaps stored as `IntPtr` in a instance field, or similar). – stakx - no longer contributing Jul 06 '12 at 15:03
1

I believe the design of that framework is inherently flawed. Let me explain:

1. [...] several objects in our framework, that by the requirement need to provide event ObjectTerminated.

This doesn't make sense. If an object has been terminated, as the event's name suggests, then I would assume that it is already gone and that I can no longer access it. This raises two questions:

  • How does something dead trigger an event? It's like a corpse talking to you from its grave, "I am dead." Do you really want this?

  • Why would anyone else be interested in reacting to such an event, if the event sender is no longer supposed to be there? What is there to clean up after the corpse has already been buried?

2. I'm not controlling their life.

Who is controlling their lifetime, then? Why isn't it their responsibility to do, or trigger, the necessary clean up work at the appropriate moment? Let me further elaborate on this very point:

3. [...] can subscribe to this event and clean-up some unmanaged stuff [...]

Where is this unmanaged stuff, and which object is responsible for handling it? If it is your own object, then why doesn't your object dispose of it — why do you instead want to trigger an event, so that someone else can dispose of the stuff? It's like me carrying out my neighbour's garbage, instead of him doing it himself. (I'm not talking about an old lady there.)

Your class would make much more sense if it looked like this:

class SomeWorkflowControlObject : IDisposable
{
     // the following event doesn't make sense, sorry.
     // public event EventHandler<> ObjectTerminated;

     private IntPtr _handleToUnmanagedResource;

     ~SomeWorkflowControlObject()
     {
         Dispose(explicitly: false);         
     }

     public void Dispose()
     {
         Dispose(explicitly: true);
         GC.SuppressFinalize(this);
     }

     protected virtual void Dispose(bool explicitly)
     {
         if (explicitly)
         {
             // free managed resources here; perhaps trigger an event 'Disposing'.
         }
         DisposeUnmanagedResource(_handleToUnmanagedResource);
     }
}

That is, it is a wrapper around some unmanaged resource, for whose disposal it is responsible itself, and noone else. Thus, there is no longer a need to trigger an event, such that someone else can dispose the unmanaged resource, which should be hidden inside your object anyway.

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
  • We are making the framework, that helps working with some hardware. But this framework doesn't have UI, so the guy who will use creates some framework objects, and actually controls their lifetime. Also as the framework designed for working with any HW, the actuall HW (that is unmanaged) is owned by the guy that is using our framework, but not by the framework itself. And there is a requirement to make the life of this guy as simple as possible. – Archeg Jul 06 '12 at 14:58
  • @Archeg: I fear your comment doesn't truly explain anything. How is your framework actually coupled to the hardware? Why, and how, does the lifetime of your framework's objects influence whether hardware resources must be cleaned up? Because, if I understood you correctly, that is why your objects must signal their end-of-lifetime through an event... If that guy controls your objects' lifetime, he will also know when your objects a destroyed. Can't he clean up his hardware resources at the same time that he frees your object(s)? Why does he need that event? – stakx - no longer contributing Jul 06 '12 at 15:06
  • There are some objects that represent HW, and they are located in that guys code (we are calling this code Handler). This Handler also contains UI, and all the HW-specific code. The framework is designed to contain these HW objects, but he knows nothing about them. So the framework is just very complicated dictionary for some specific objects. Yes, I guess he can clean these objects by himself, I think these requirements are designed to force him not to forget to do that. These req. are dictated from client as in future he and his clients will be that guys who use our framework. – Archeg Jul 06 '12 at 15:28
  • I have discussed with architector this problem, and we agreed that it will be the responsibility of that guy, to call a method Terminate(), that will populate the event Terminated, that this guy can subscribe and clean up the resources. (So it will be something like (3) in your first answer) That will make it a little decoupled, as cal to Terminate() would be made from UI (form close), and subscribtion to Terminated would be in some HW code. So I will mark one of your answers as correct, but feel free to add something if you have something :) And thanks for help – Archeg Jul 06 '12 at 15:31