Search code examples
c#genericsunity-game-enginedelegatesaction

C# Delegate Type As Generic Constraint


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.


Solution

  • 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 ...
    }