Casting a Delegate to a Generic delegate in C#

2019-07-08 10:35发布

问题:

Introduction

I'm using delegates to pass along and store styling logic for individual form Controls. 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 StyleDels). 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 StyleDels 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?

回答1:

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.