1

I have a function:

private void SetupCallbacks()
{
        Type actionType = Type.GetType(CardData.ActionFile);
        if (actionType == null)
            return;

        // To get any particular method from actionType, I have to do the following
        MethodInfo turnStarted = actionType.GetMethod(CardData.TurnStartedMethod);
        if (turnStarted != null)
        {
            Delegate d = Delegate.CreateDelegate(typeof(Action<bool>), turnStarted);
            Action<bool> turnStartedAction = (Action<bool>)d;
            TurnManager.Instance.OnTurnStarted += turnStartedAction;
        }

        ...
}

actionType is a class that contains several static methods. These methods are stored as strings in the CardData object. I provided an example using the OnTurnStarted callback. It is very clunky to write out all that code repeatedly each time I want to add another callback. I've tried creating a function:

private void SetupCallback<TDelegate>(Type actionType, string method, TDelegate delagateToAddThisTo) where TDelegate : Delegate
{
    MethodInfo methodInfo = actionsContainerClass.GetMethod(method);
        if (methodInfo != null)
        {
            Delegate d = Delegate.CreateDelegate(typeof(Action<Card>), methodInfo);
            TDelegate t = (TDelegate)d;
            delagateToAddThisTo += t;
        }
}

However, where TDelegate : Delegate doesn't work. I can't just do some type checking in the method (ie:

if(typeof(TDelegate).IsSubclassOf(typeof(Delegate)) == false)
{
  throw new InvalidOperationException("Card::SetupCallback - " + typeof(TDelegate).Name + " is not a delegate");
}

because delagateToAddThisTowhich is of type TDelegate and needs to be able to be added to.

Thank you in advance.

felix-b
  • 8,178
  • 1
  • 26
  • 36
  • I guess you realize that even if you make it somehow work, `delagateToAddThisTo += t` will have no effect. For events `+= ` is translated to `add` method and for delegates `delagateToAddThisTo` must be a variable (i.e. `ref`) – Ivan Stoev Jul 05 '17 at 17:27
  • Possible duplicate of [C# Generics won't allow Delegate Type Constraints](https://stackoverflow.com/questions/191940/c-sharp-generics-wont-allow-delegate-type-constraints) – BatteryBackupUnit Jun 01 '18 at 11:16

2 Answers2

4

C# doesn't allow constraining generic type parameters with delegate types. Your only option to validate delegate types is at runtime.

For the same reason, you won't be able to use the += operator inside the CreateCallback method. But if += is moved to the caller (SetupCallbacks), and the CreateCallback only creates and returns the delegate, it can still look quite elegant:

// this code is in SetupCallbacks method
// Action<...> delegates are just examples

TurnManager.Instance.OnTurnStarted += 
    CreateCallback<Action<string, int>>(actionType, CardData.TurnStartedMethod);

TurnManager.Instance.OnTurnStopped += 
    CreateCallback<Action<string, int, TimeSpan>>(actionType, CardData.TurnStoppedMethod);

Where the CreateCallback method is as follows:

private TDelegate CreateCallback<TDelegate>(Type actionType, string method)
    where TDelegate : class
{
    if (!typeof(Delegate).IsAssignableFrom(typeof(TDelegate)))
    {
        throw new InvalidOperationException("Card::SetupCallback - " + typeof(TDelegate).Name + " is not a delegate");
    }

    MethodInfo methodInfo = actionType.GetMethod(method);

    if (methodInfo != null)
    {
        // the following line will also validate compatibility of delegate types
        Delegate nonTypedDelegate =  methodInfo.CreateDelegate(typeof(TDelegate));
        TDelegate typedDelegate = (TDelegate)(object)nonTypedDelegate;
        return typedDelegate;
    }            

    return null;
}

Provided that in my example, TurnManager class looks like this:

public class TurnManager
{
    public static TurnManager Instance 
    { 
        get { /* ....... */ }
    }

    public Action<string, int> OnTurnStarted { get; set; }
    public Action<string, int, TimeSpan> OnTurnStopped { get; set; }

    //... other members ...
}
felix-b
  • 8,178
  • 1
  • 26
  • 36
1

Starting from C# 7.3 it is possible.

Example from Microsoft docs

public class UsingEnum<T> where T : System.Enum { }

public class UsingDelegate<T> where T : System.Delegate { }

public class Multicaster<T> where T : System.MulticastDelegate { }

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint

Albert Lyubarsky
  • 438
  • 1
  • 7
  • 15