April 25, 2008

Events, delegates, bugs and memory leaks

Automatic memory management is one of the greatest things about C# and the CLR. Delegates and events are another. Put these together, though, and you might get more than you've bargained for.

One thing you must always be aware of is that when you register an instance method to an event, the MulticastDelegate behind the event creates and then maintains a reference to your instance. So unless you explicitly unregister, an event owner with a wider scope than your observer will drag the latter along and keep it from being collected by the GC, thus causing a memory leak. Moreover, even though it is long out of the scope of your own code, your observer's method will still be called when the event is raised. This is generally undesirable and at least a waste of CPU cycles if not a cause for more serious bugs. Let's see some code that demonstrates this behaviour.

   1:  public class RealWorldSystem
   2:  {
   3:      public event EventHandler DoneSomething;
   4:      public void DoSomething()
   5:      {
   6:          EventHandler h = this.DoneSomething;
   7:          if (h != null)
   8:          {
   9:              h(this, new EventArgs());
  10:          }
  11:      }
  12:  }

  13:  public class Logger
  14:  {
  15:      private static int instanceCount;
  16:      private int whoAmI;
  17:  
  18:      private RealWorldSystem subject;
  19:      public Logger(RealWorldSystem subject)
  20:      {
  21:          if (subject == null)
  22:          {
  23:              throw new ArgumentNullException();
  24:          }
  25:          whoAmI = instanceCount;
  26:          instanceCount++;
  27:          this.subject = subject;
  28:          subject.DoneSomething += new EventHandler(LogSomething);
  29:      }
  30:      private void LogSomething(object sender, EventArgs e)
  31:      {
  32:          Console.Write(whoAmI);
  33:      }
  34:  }

Now imagine that your RealWorldSystem exposes a whole lot of events through which it notifies the outside world of its actions. You're writing a monitoring application and you want to let your users select flexible logging options. For instance, some system actions should be logged only when the system load exceeds a certain threshold, and then when the load diminishes again, logging should stop. In terms of our mock-up classes, this would look like:


  35:  RealWorldSystem system = new RealWorldSystem();
  36:  for (int i = 0; i < 100; i++)
  37:  {
  38:      Logger l = new Logger(system);
  39:      system.DoSomething();
  40:  }

Running the code shows that at each iteration i, all i-1 Logger instances created before are still responding to the DoneSomething event, even if none of them are in scope anymore.

Workarounds for this problem revolve around two approaches. The first one is implementing explicit Unregister methods in observer classes. The drawback with this one is that keeping track of all event registrations in a medium-or-above application can quickly become unmanageable. The second is more complex and makes use of WeakReferences to implement so called WeakDelegates.

Here's more on this from better writers: [1], [2], [3].

April 20, 2008

Mitică şi Primăria

Când şi-a anunţat candidatura pentru Primăria oraşului Iaşi, Dumitru Oprea şi-a declarat intenţia de a fi un "primar european". Implicit, gândeam eu, campania electorală a domnului rector urma să fie, şi ea, una "europeană". Ei bine, aceasta stă să înceapă, însă în pre-campanie, candidatul PD-L a etalat un modus operandi pur românesc. Recunosc că nu posed cunoştinţe de teoria comunicării sau de strategie electorală, însă campanii al căror fel principal este atacarea contracandidaţilor, cu garnitură de miştocăreală şi cu un pic de dezinformare, pentru gust, am mai văzut şi mi-a ajuns.

Poate că aceste metode funcţionează. Poate că îi vor aduce voturile unor anumite categorii de alegători. Poate că, dacă va fi ales, Dumitru Oprea va fi, totuşi, un primar excepţional. Însă ca alegător, ca ieşean, ca cetaţean al UE şi ca student (în curând absolvent) al Universităţii Alexandru Ioan Cuza, aş prefera să văd un duel electoral între Prof. Dr. Dumitru Oprea şi Ing. Gheorghe Nichita, nu între Mitică şi Gigi.

April 15, 2008

Despre traininguri şi mesagerie instantă

Am fost zilele astea la două training-uri de câte 2 ore fiecare ţinute de Adrian Rîndaşu de la Edurom. Temele au fost Leadership şi Comunicare iar calitatea a fost de la bună în sus. Dacă auziţi de vreun training ţinut de tipul ăsta, vă recomand să participaţi. Omul ştie despre ce vorbeşte, trădează o experienţă profesională destul de consistentă, zic eu, şi nu se ţine de joculeţe şi alte prostioare dintr-astea care fac multe "traininguri" sa fie trecute la capitolul pierdere de vreme.

În fine, o chestie interesantă despre care a vorbit trainerul a fost costul întreruperilor. Mai precis, impactul negativ pe care îl au întreruperile asupra productivităţii unei persoane angrenate în rezolvarea unei sarcini oarecare. Conform unor studii, cică, dacă eşti întrerupt de 3 ori în timpul redactării unui raport care ar dura în mod normal 20 de minute, sarcina ajunge să dureze 40 de minute - datorită timpului de amortizare (în care vezi unde rămăseseşi, îţi reiei ideile etc.).

Ei, lucrul ăsta m-a făcut să mă gândesc empatic la oamenii pe care îi mai întrerup din când în când cu câte un mesaj pe YM, deşi statusul lor zice "busy". Promit că nu mai fac. De fapt, ştiu că se întâmplă de multe ori să te-apuci de lucru şi să uiţi să schimbi statusul, aşa că o să mă feresc să contactez până şi persoane "available". Ce vreau să spun e că per total vom vorbi mai puţin pe YM de-acum încolo, de dragul productivităţii voastre. Sunt sigur că-mi veţi mulţumi în curând.

April 06, 2008

Use delegate calls instead of MethodInfo.Invoke

Reflection operations may allow you to write flexible and extensible code, but some of them also come with a serious performance hit. Type inspection and dynamic method invocation are probably the most representative.

If you ever needed to make a lot of dynamic calls using MethodInfo.Invoke but you knew that most of the time you would be calling the same small number of methods, you've probably thought about caching the MethodInfo instances, thus saving time by not having to obtain them again and again through inspection (via Type.GetMethod).

An even better solution, however, would be to bind a delegate to each MethodInfo using Delegate.CreateDelegate, cache the delegates and perform all your calls using these delegates instead of MethodInfo.Invoke. Delegate calls are significantly faster than dynamic invocation so the increase in performance will be quite noticeable.

Here's a great article on reflection where you can read more about the Why and the How.

April 02, 2008

Check a type against a generic type definition

Should you want to check whether some object obj is of a constructed type (or closed generic type) obtained by specifying a given generic type definition (or open generic type), say A<T>, you'll find that the task is not the one-liner you would hope it to be.

First of all, the is operator does not work on open generic types, so a check like obj is A<> will not compile.

What will compile, however, is typeof(A<>) which will get you a Type instance representing the open generic type. This is a good starting point and, naturally, the next thing to try would be the IsSubclassOf(Type) method exposed by the Type class. Alas, this works not. The method will always return false when taking an open generic type as argument because in any concrete class hierarchy, only closed generic types can appear.

The solution I've found takes advantage of the IsGenericType property and the GetGenericTypeDefinition() method exposed by the same Type class:

   1:  public static bool Check(object obj)
   2:  {
   3:      Type soughtType = typeof(A<>);
   4:      Type inspectedType = obj.GetType();
   5:      while(!inspectedType.Equals(typeof(object)))
   6:      {
   7:          if (inspectedType.IsGenericType)
   8:          {
   9:              Type gen = inspectedType.GetGenericTypeDefinition();
  10:              if (soughtType.Equals(gen))
  11:              {
  12:                  return true;
  13:              }
  14:          }
  15:          inspectedType = inspectedType.BaseType;
  16:      }
  17:      return false;
  18:  }


Ignoring the NullReferenceException that's just waiting to happen, notice that the code goes up the class hierarchy one base type at a time [15], inquiring whether the currently inspected type is a constructed type [7] and whether it specifies the very generic type definition we want to check against [10]. The search stops when it reaches the root of all class hierarchies, namely object [5].