Duncan Duncan - 2 months ago 8
C# Question

Casting a Delegate to a Generic in C#

Introduction



I'm using delegates to pass along and store styling logic for individual form
Control
s. For example, I have a delegate containing some
Button
-styling logic like this:

button.BackColor = Color.Red;
button.ForeColor = Color.White;
button.FlatStyle = FlatStyle.Flat;


Of course there are many different other type of controls, like Labels, Panels, etc. So to store all these delegates I use a
Dictionary<Type, Delegate>
.

Although, the delegate itself looks like this:

delegate void StyleDel<in T>(T control) where T : Control;


So in order to use the logic inside the dictionary, the
Delegate
must be cast to
StyleDel<T>
first - whatever
T
might be at that moment.




The situation



After all of the styling is initialised and stored, the styling must be applied (using the
StyleDel
s). For this I made a function
StyleControl(control)
.

This function looks at the type of the control (e.g. a
Button
) and finds the corresponding
StyleDel
from the
Dictionary
, which in its turn applies the (
Button
-)styling.

public void StyleControl<T>(T control) where T : Control
{
Delegate storedDel;
if (_dict.TryGetValue(control.GetType(), out storedDel))
{
// Cast Delegate to StyleDel
var styleDel = (StyleDel<T>) storedDel;

// Execute StyleDel
styleDel(control);
}
}


The
StyleDel
s are added to the dictionary with the
Add
function below:

public bool Add<T>(StyleDel<T> styleDel) where T : Control
{
var inDict = _dict.ContainsKey(typeof(T));
if (!inDict) _dict[typeof(T)] = styleDel;
return !inDict;
}


And the
StyleControl
function is called by another function, which makes sure everything is styled recursively:

public void Style<T>(T parent) where T : Control
{
StyleControl(parent);

// The problem might have to do with this
foreach (Control child in parent.Controls) Style(child);
}





The problem



An
InvalidCastException
is thrown, saying a
StyleDel<Button>
cannot be converted to
StyleDel<Control>
. So I believe it's saying that
T
is seen as a
Control
at this point, while it's actually a
Button
.

How do I cast this
Delegate
to a
StyleDel<Button>
successfully?

Answer

You can achieve this by adding a level of inderection; create a lambda that calls your delegate casting the argument to the right type:

Dictionary<Type, StyleDel<Control>> _dict = ...

public bool Add<T>(StyleDel<T> styleDel) where T : Control
{
    var inDict = _dict.ContainsKey(typeof(T)); 
    if (!inDict) _dict[typeof(T)] = d => StyleDel((T)d);
    return inDict;
}

At first glance this might seem to not be type safe, but in this particular case it will be because the delegate is stored in a dictionary with the argument's true type as it's key. Intended usage will therefore always ensure that the delegate is always called with a correctly typed argument and a runtime cast exception will not happen.