0

Please see example below. Even though the reference to obj is set to null, the obj is not released and Obj_Elapsed continues printing i. Notice there is no reference to the timer out of the scope of the ObjectWithTimer constructor.

public class Program {

    public static void Main(string[] args)
    {
        object obj = new ObjectWithTimer();
        Console.ReadLine();
        Console.WriteLine("obj=null");
        obj = null;
        Console.ReadLine();
    }
}

public class ObjectWithTimer
{
    private int i;
    public System.Timers.Timer t;
    public ObjectWithTimer()
    {
        t = new System.Timers.Timer(5000);
        t.Elapsed += Obj_Elapsed;
        t.Enabled = true;
    }

    public void Obj_Elapsed(object sender, ElapsedEventArgs e)
    {
        i++;
        Console.WriteLine(i);
    }
}
  • 1
    You seem to provide only partial code of your experiment - I don't see any `GC.Collect()` calls... unless your question is "why setting object to `null` does not cause GC"... – Alexei Levenkov Feb 11 '20 at 21:08
  • This particular [answer](https://stackoverflow.com/a/3597452/477420) in liked duplicate goes into a lot of details on difference between `System.Timers.Timer` and `System.Threading.Timer` behavior. Note that you indeed can find that all yourself by using VS performance profiler (or any other .NET memory profiler) to see if particular object has references to it at any point in time. – Alexei Levenkov Feb 11 '20 at 21:40

1 Answers1

1

Setting null in this instance and/or going out of scope is not good enough, The Timer has resources it's managing and needs to be cleaned up.

Since System.Timers.Timer Implements IDisposable, ideally so should your wrapper class

public class ObjectWithTimer : IDisposable
{
   // Flag: Has Dispose already been called?
   private bool _disposed = false;

   private int _i;
   public System.Timers.Timer Timer { get; }
   public ObjectWithTimer()
   {
      Timer = new System.Timers.Timer(5000);
      Timer.Elapsed += Obj_Elapsed;
      Timer.Enabled = true;
   }

   public void Obj_Elapsed(object sender, ElapsedEventArgs e)
   {
      _i++;
      Console.WriteLine(_i);
   }

   // Public implementation of Dispose pattern callable by consumers.
   public void Dispose() =>Dispose(true);


   // Protected implementation of Dispose pattern.
   protected virtual void Dispose(bool disposing)
   {
      if (_disposed) return; 
      if (disposing) Timer?.Dispose();  
      _disposed = true;
   }
}

You should in turn then also dispose of the wrapper called at some stage, and not just set it null, the easiest way to do that is with the using statement

Provides a convenient syntax that ensures the correct use of IDisposable objects.

public static void Main(string[] args)
{
    using(object obj = new ObjectWithTimer())
    {
       Console.ReadLine();
       Console.WriteLine("obj=null");
    }
    Console.ReadLine();
}

Implementing a Dispose method

You implement a Dispose method to release unmanaged resources used by your application. The .NET garbage collector does not allocate or release unmanaged memory.

Note : This wasn't a complete tutorial on the IDisposable pattern, just an example. Please do your own research and diligence on this implementation


Additional Resouces

halfer
  • 19,824
  • 17
  • 99
  • 186
TheGeneral
  • 79,002
  • 9
  • 103
  • 141