C#泛型不允许委托类型约束


79

是否可以在C#中定义一个类,使得

class GenericCollection<T> : SomeBaseCollection<T> where T : Delegate

我一生无法在.NET 3.5中完成昨晚的工作。我尝试使用

delegate, Delegate, Action<T> and Func<T, T>

在我看来,这在某种程度上应该是允许的。我正在尝试实现自己的EventQueue。

我最终做了这个[原始近似值介意你]。

internal delegate void DWork();

class EventQueue {
    private Queue<DWork> eventq;
}

但是后来我失去了为不同类型的函数重用相同定义的能力。

有什么想法吗?

Answers:


66

许多类不能作为通用约束使用-枚举是另一个。

对于委托,您可以获得的最接近的是“:class”,也许使用反射来检查(例如,在静态构造函数中)T委托:

static GenericCollection()
{
    if (!typeof(T).IsSubclassOf(typeof(Delegate)))
    {
        throw new InvalidOperationException(typeof(T).Name + " is not a delegate type");
    }
}

8
+1代表:1)使用静态构造函数,以及2)由于围绕类型初始化的奇怪调试条件而包含详细消息。
山姆·哈威尔

6
@MarcGravell:不会在静态初始化程序违反中引发异常CA1065: Do not raise exceptions in unexpected locations……我一直在假设您应该使用自定义代码分析规则来查找类的无效用法,这些用法通常在运行时不可用。
myermian 2012年

3
从C#7.3(2018年5月发布)开始,可以这样约束where T : Delegate``(并且有人在下面发布了有关该问题的新答案)。
杰普·斯蒂格·尼尔森

16

是的,它有可能在C#7.3,家庭增加的制约因素包括EnumDelegateunmanaged类型。您可以毫无问题地编写以下代码:

void M<D, E, T>(D d, E e, T* t) where D : Delegate where E : Enum where T : unmanaged
    {

    }

从文档

从C#7.3开始,可以使用非托管约束来指定type参数必须是不可为null的非托管类型。非托管约束使您可以编写可重用的例程,以与可以作为内存块进行操作的类型一起使用

有用的链接:

Microsoft Build 2018中C#的未来

C#7.3的新功能是什么?


是的,自2018年5月起在C#7.3中是可能的,您可以在此处查看发行说明
杰普·斯蒂格·尼尔森

13

编辑:这些文章中提出了一些建议的解决方法:

http://jacobcarpenters.blogspot.com/2006/06/c-30-and-delegate-conversion.html

http://jacobcarpenters.blogspot.com/2006_11_01_archive.html


C#2.0规范中,我们可以看到(20.7,约束):

类类型约束必须满足以下规则:

  • 该类型必须是类类型。
  • 该类型不得密封。
  • 该类型不能为以下类型之一:System.Array,System.Delegate,System.Enum或System.ValueType
  • 类型不能是对象。因为所有类型都源自对象,所以如果允许,这样的约束将无效。
  • 给定类型参数的一个约束最多可以是一个类类型。

并且确定VS2008会吐出一个错误:

error CS0702: Constraint cannot be special class 'System.Delegate'

有关此问题的信息和调查,请阅读此处


10

如果您愿意依赖IL Weaver进行编译时依赖,则可以使用Fody做到这一点。

使用此插件到Fody https://github.com/Fody/ExtraConstraints

您的代码如下所示

public class Sample
{
    public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
    {        
    }
    public void MethodWithEnumConstraint<[EnumConstraint] T>()
    {
    }
} 

并编译为此

public class Sample
{
    public void MethodWithDelegateConstraint<T>() where T: Delegate
    {
    }

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}

链接断开。你现在有吗?
贾斯汀·摩根

3

委托已经支持链接。这不符合您的需求吗?

public class EventQueueTests
{
    public void Test1()
    {
        Action myAction = () => Console.WriteLine("foo");
        myAction += () => Console.WriteLine("bar");

        myAction();
        //foo
        //bar
    }

    public void Test2()
    {
        Action<int> myAction = x => Console.WriteLine("foo {0}", x);
        myAction += x => Console.WriteLine("bar {0}", x);
        myAction(3);
        //foo 3
        //bar 3
    }

    public void Test3()
    {
        Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; };
        myFunc += x => { Console.WriteLine("bar {0}", x); return x + 1; };
        int y = myFunc(3);
        Console.WriteLine(y);

        //foo 3
        //bar 3
        //4
    }

    public void Test4()
    {
        Func<int, int> myFunc = x => { Console.WriteLine("foo {0}", x); return x + 2; };
        Func<int, int> myNextFunc = x => { x = myFunc(x);  Console.WriteLine("bar {0}", x); return x + 1; };
        int y = myNextFunc(3);
        Console.WriteLine(y);

        //foo 3
        //bar 5
        //6
    }

}

那不是我真正想要的功能...我试图对我的通用类进行类型限制...
Nicholas Mancuso

3

我遇到了需要Delegate内部处理但我想要通用约束的情况。具体来说,我想使用反射添加事件处理程序,但是我想对委托使用通用参数。下面的代码不起作用,因为“ Handler”是类型变量,并且编译器不会强制转换HandlerDelegate

public void AddHandler<Handler>(Control c, string eventName, Handler d) {
  c.GetType().GetEvent(eventName).AddEventHandler(c, (Delegate) d);
}

但是,您可以传递一个为您执行转换的函数。convert接受Handler参数并返回Delegate

public void AddHandler<Handler>(Control c, string eventName, 
                  Func<Delegate, Handler> convert, Handler d) {
      c.GetType().GetEvent(eventName).AddEventHandler(c, convert(d));
}

现在,编译器很高兴。调用该方法很容易。例如,将KeyPress事件附加到Windows窗体控件上:

AddHandler<KeyEventHandler>(someControl, 
           "KeyPress", 
           (h) => (KeyEventHandler) h,
           SomeControl_KeyPress);

SomeControl_KeyPress事件目标在哪里。关键是转换器lambda-它不起作用,但是可以说服编译器为您提供了有效的委托。

(开始280Z28)@Justin:为什么不使用它?

public void AddHandler<Handler>(Control c, string eventName, Handler d) { 
  c.GetType().GetEvent(eventName).AddEventHandler(c, d as Delegate); 
} 

(完280Z28)


1
@Justin:我编辑了您的答案以在评论的末尾加上注释,因为它有一个代码块。
山姆·哈威尔

2

如上所述,不能将Delegates和Enum作为通用约束。System.Object并且System.ValueType也不能用作一般约束。

如果您在IL中构建适当的调用,则可以解决该问题。它将正常工作。

这是乔恩·斯基特(Jon Skeet)的一个很好的例子。

http://code.google.com/p/unconstrained-melody/

我从乔恩·斯凯特(Jon Skeet)的《C#in Depth》(第三版)一书中获得了参考。


1

根据MSDN

编译器错误CS0702

约束不能是特殊的“标识符”类以下类型不能用作约束:

  • 系统对象
  • 系统数组
  • 系统代理
  • 系统枚举
  • System.ValueType。

为什么在这里重复这个问题?您什么都不要告诉我们。
Elmue
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.