Skip to content

Proposal: Support System.Delegate as a generic constraint #158

@HaloFour

Description

@HaloFour

Problem

In C# it is currently not legal to specify that a generic type parameter be constrained to System.Delegate.

// CS0702: Constraint cannot be special class 'System.Delegate'
public TDelegate Combine(TDelegate first, params TDelegate[] others) where TDelegate : System.Delegate {
   ...
}

This is a limitation imposed only by the C# compiler. The .NET runtime fully supports generic type constraints of System.Delegate and the C# compiler does support consuming said constraints from assemblies that have been compiled from languages without that limitation:

extension method TDelegate.Combine<TDelegate>(params others: array of TDelegate): TDelegate; where TDelegate is System.Delegate
EventHandler handler1 = (s, e) => Console.WriteLine("this");
EventHandler handler2 = (s, e) => Console.WriteLine("that");
EventHandler combined = handler1.Combine(handler2);  // calling Oxygene extension method

Proposal:

Remove the artificial limitation and allow a generic type constraint to be of type System.Delegate. Since this syntax is currently illegal in C# doing so would not impact any existing code.

Note that I am not suggesting to support the keyword delegate. My opinion is that the keyword be left unsupported for potential future constraint work that would allow specifying a required signature for the delegate which would expose the ability to invoke the delegate. The runtime does not currently support such a notion and it would be unenforceable.

Use Cases

Type-safe helper methods to combine delegates:

public static TDelegate Combine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : System.Delegate {

    return (TDelegate)Delegate.Combine(source, target);
}

Helper methods to subscribe/unsubscribe event fields:

public static void Subscribe<TDelegate>(ref TDelegate field, TDelegate target)
    where TDelegate : System.Delegate {

    // this is the logic emitted by the C# compiler when you define an event with the default adder/remover methods
    TDelegate previous = null;
    TDelegate current = field;
    do {
        previous = current;
        TDelegate proposed = (TDelegate)Delegate.Combine(previous, target);
        current = Interlocked.Exchange<TDelegate>(ref field, proposed, previous);
    } while (previous != current);
}

...

public EventHandler MyEvent {
    add {
        // custom logic here
        // explicitly implementing an event leaves the developer on their own for handling the
        // subscription and using += directly against the event field is inherently not thread-safe
        EventHelper.Subscribe<EventHandler>(ref MyEvent, value); // thread-safe subscription
    }
    remove {
        EventHelper.Unsubscribe<EventHandler>(ref MyEvent, value); // thread-safe unsubscription
    }
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions