HelterSkelter HelterSkelter - 3 months ago 7
C# Question

How does the delegate invocation list is built?

I'm trying to understand how combining delegates works. In the following code, I changed the usual implementation of

set
(with a simple assignment, using
=
operator) into an ill-formed one (by using the
+=
operator).

So the following code:

using System;
using System.Linq;

class Cooler
{
public void OnTemperatureChanged(float newTemperature) {}
}

class Heater
{
public void OnTemperatureChanged(float newTemperature) {}
}

public class Thermostat
{
private Action<float> _OnTemperatureChange;

public Action<float> OnTemperatureChange
{
get
{ return _OnTemperatureChange; }
set
{
_OnTemperatureChange += value; // note the +=
}
}

public void PrintRegistered()
{
if (OnTemperatureChange != null)
{
foreach (Delegate existingHandler in _OnTemperatureChange.GetInvocationList())
{
Console.WriteLine(existingHandler.Target + "." + existingHandler.Method);
}
}
}
}

class Program
{
public static void Main()
{
Thermostat thermostat = new Thermostat();
Heater heater = new Heater();
Cooler cooler = new Cooler();

thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;
thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
thermostat.PrintRegistered();
}
}


Prints:

Cooler.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)


Of course, If I change back the
set
to its usual implementation, we'll see a single
OnTemperatureChanged
for
Cooler
and
Heater
.

I'd like to understand why setting the delegate this way resulted in this form of invocation list?

usr usr
Answer

+= for delegates calls Delegate.Combine. thermostat.OnTemperatureChange += cooler.OnTemperatureChanged; calls this twice: Once in Main and once in set_OnTemperatureChange. So it's a banal bug that causes the side effect two times.

thermostat.OnTemperatureChange += cooler.OnTemperatureChanged adds Cooler.

thermostat.OnTemperatureChange += heater.OnTemperatureChanged takes Cooler, converts it into Cooler, Heater, then calls set which combines it with the old value causing this to become Cooler, Cooler, Heater.

I admit I found this very unintuitive but it's due to a banal bug.

Adding a 3rd heater line results in:

Cooler.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)

Adding another heater line results in:

Cooler.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Cooler.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)
Heater.Void OnTemperatureChanged(Single)

It always keeps the same prefix, the the same prefix again, then heater.

If you want a recommendation: Use event Action<float> OnTemperatureChange; and this goes away.

Comments