85

Is it possible to define a class in C# such that

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

I couldn't for the life of me accomplish this last night in .NET 3.5. I tried using

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

It seems to me that this should be allowable in some way. I'm trying to implement my own EventQueue.

I ended up just doing this [primitive approximation mind you].

internal delegate void DWork();

class EventQueue {
    private Queue<DWork> eventq;
}

But then I lose the ability to reuse the same definition for different types of functions.

Thoughts?

Peter O.
  • 32,158
  • 14
  • 82
  • 96
Nicholas Mancuso
  • 11,599
  • 6
  • 45
  • 47

8 Answers8

68

A number of classes are unavailable as generic contraints - Enum being another.

For delegates, the closest you can get is ": class", perhaps using reflection to check (for example, in the static constructor) that the T is a delegate:

static GenericCollection()
{
    if (!typeof(T).IsSubclassOf(typeof(Delegate)))
    {
        throw new InvalidOperationException(typeof(T).Name + " is not a delegate type");
    }
}
Eduard Malakhov
  • 1,095
  • 4
  • 13
  • 25
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 8
    +1 for: 1) using the static constructor and 2) including a detailed message due to weird debugging conditions surrounding type initialization. – Sam Harwell Jan 18 '10 at 18:57
  • 6
    @MarcGravell: Doesn't throwing an exception in a static initializer violate `CA1065: Do not raise exceptions in unexpected locations` ... I was always under the assumption that you should use a custom code analysis rule to find invalid usages of your class that aren't normally available at run-time. – myermian Nov 28 '12 at 22:03
  • 3
    Starting in C# 7.3 (released May 2018), it is allowed to constrain like this, `where T : Delegate`, (and someone posted a new answer about that below). – Jeppe Stig Nielsen May 29 '18 at 09:55
21

Yes it's possible in C# 7.3, Constraints family increased to include Enum, Delegate and unmanaged types. You can write this code without a problem:

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

    }

From Docs:

Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be a non-nullable unmanaged type. The unmanaged constraint enables you to write reusable routines to work with types that can be manipulated as blocks of memory

Useful links:

The future of C#, from Microsoft Build 2018

What's new in C# 7.3?

mshwf
  • 7,009
  • 12
  • 59
  • 133
  • Yes, it is possible in C# 7.3 (since May 2018), and you can see [release notes here](https://learn.microsoft.com/en-us/visualstudio/releasenotes/vs2017-relnotes#csharp). – Jeppe Stig Nielsen May 29 '18 at 09:53
  • This should be the new accepted answer, the current one is from 2008.. Very outdated now. – Issung Jun 09 '21 at 00:21
  • 1
    So you can use `Delegate`, meaning "any function" - but there is still no way to constrain a type parameter to a specific `delegate` type? When would anyone want `Delegate` without specifying the parameters and return-type? Doesn't seem useful. – mindplay.dk Jul 12 '22 at 18:42
13

Edit: Some proposed work-arounds are proposed in these articles:

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

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


From the C# 2.0 specification we can read (20.7, Constraints):

A class-type constraint must satisfy the following rules:

  • The type must be a class type.
  • The type must not be sealed.
  • The type must not be one of the following types: System.Array, System.Delegate, System.Enum, or System.ValueType.
  • The type must not be object. Because all types derive from object, such a constraint would have no effect if it were permitted.
  • At most one constraint for a given type parameter can be a class type.

And sure enough VS2008 spits out an error:

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

For info and investigation on this issue read here.

Jorge Ferreira
  • 96,051
  • 25
  • 122
  • 132
10

If you are willing to take a compile time dependency on an IL Weaver you can do this with Fody.

Using this addin to Fody https://github.com/Fody/ExtraConstraints

Your code can look like this

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

And be compiled to this

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

    public void MethodWithEnumConstraint<T>() where T: struct, Enum
    {
    }
}
Şafak Gür
  • 7,045
  • 5
  • 59
  • 96
Simon
  • 33,714
  • 21
  • 133
  • 202
4

I came across a situation where I needed to deal with a Delegate internally but I wanted a generic constraint. Specifically, I wanted to add an event handler using reflection, but I wanted to use a generic argument for the delegate. The code below does NOT work, since "Handler" is a type variable, and the compiler won't cast Handler to Delegate:

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

However, you can pass a function that does the conversion for you. convert takes a Handler argument and returns a Delegate:

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

Now the compiler is happy. Calling the method is easy. For example, attaching to the KeyPress event on a Windows Forms control:

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

where SomeControl_KeyPress is the event target. The key is the converter lambda - it does no work, but it convinces the compiler you gave it a valid delegate.

(Begin 280Z28) @Justin: Why not use this?

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

(End 280Z28)

Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
Justin Bailey
  • 1,487
  • 11
  • 15
3

Delegate already supports chaining. Doesn't this meet your needs?

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
    }

}
Amy B
  • 108,202
  • 21
  • 135
  • 185
2

As mentioned above, you cannot have Delegates and Enum as a generic constraint. System.Object and System.ValueType also cannot be used as a generic constraint.

The work around can be if you construct an appropriate call in you IL. It will work fine.

Here is a good example by Jon Skeet.

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

I have taken my references from Jon Skeet's book C# in Depth, 3rd edition.

Stephen Kennedy
  • 20,585
  • 22
  • 95
  • 108
maxspan
  • 13,326
  • 15
  • 75
  • 104
1

According to MSDN

Compiler Error CS0702

Constraint cannot be special class 'identifier' The following types may not be used as constraints:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Rahul Nikate
  • 6,192
  • 5
  • 42
  • 54