【问题标题】:C# Delegate Type As Generic ConstraintC# 委托类型作为通用约束
【发布时间】:2017-07-05 17:09:23
【问题描述】:

我有一个函数:

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 是一个包含多个静态方法的类。这些方法作为字符串存储在 CardData 对象中。我提供了一个使用 OnTurnStarted 回调的示例。每次我想添加另一个回调时,重复写出所有这些代码是非常笨拙的。我试过创建一个函数:

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;
        }
}

但是,where TDelegate : Delegate 不起作用。我不能只在方法中进行一些类型检查(即:

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

因为delagateToAddThisTo属于TDelegate类型,需要能够添加。

提前谢谢你。

【问题讨论】:

  • 我猜你意识到即使你让它工作了,delagateToAddThisTo += t 也没有任何效果。对于事件,+= 被转换为 add 方法,对于委托,delagateToAddThisTo 必须是一个变量(即ref

标签: c# generics unity3d delegates action


【解决方案1】:

C# 不允许使用委托类型来约束泛型类型参数。验证委托类型的唯一选择是在运行时。

出于同样的原因,您将无法在CreateCallback 方法中使用+= 运算符。但是如果将+= 移动到调用者(SetupCallbacks),而CreateCallback 只创建并返回委托,它仍然可以看起来相当优雅:

// 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);

其中CreateCallback方法如下:

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;
}

假设在我的示例中,TurnManager 类如下所示:

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

【讨论】:

  • 谢谢!完美运行。
【解决方案2】:

从 C# 7.3 开始有可能。

来自 Microsoft 文档的示例

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://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint

【讨论】:

    猜你喜欢
    • 2010-09-16
    • 2022-01-16
    • 1970-01-01
    • 1970-01-01
    • 2014-09-08
    • 1970-01-01
    • 2014-12-07
    • 2023-03-05
    • 2021-08-29
    相关资源
    最近更新 更多