Skip to content

Registering contravariant event handlers can throw at runtime #131774

Description

@jlao

Description

I had a situation where I was trying to delegate an event handler to another one with a more derived argument. This is possible now in .NET 10 because EventHandler is contravariant. This compiles but I discovered that it can throw if you register handlers of different types on different events (see repro).

It looks like the MulticastDelegate being used under the covers is doing a strict type check so it doesn't allow contravariance in this specific case. In the first two lines I can see in the decompiled IL that a EventHandler<Bar> is constructed but in the third line a EventHandler<Foo> is constructed.

Reproduction Steps

// This is a LINQPad script

void Main()
{
  Handler += BarHandler; // this works
  Handler += FooHandler; // this works
  DelegatingHandler += FooHandler; // this throws. works if I comment out the lines above.

  Handler?.Invoke(null, new Bar());
}

// You can define other methods, fields, classes and namespaces here

event EventHandler<Bar> Handler;

event EventHandler<Foo> DelegatingHandler
{
  add => Handler += value;
  remove => Handler -= value;
}

void BarHandler(object sender, Bar arg)
{
  arg.Dump();
}

void FooHandler(object sender, Foo arg)
{
  arg.Dump();
}

class Foo
{
}

class Bar : Foo
{
}

Expected behavior

Doesn't throw.

Actual behavior

Throws an ArgumentException:

Delegates must be of the same type.
   at System.MulticastDelegate.CombineImpl(Delegate follow)
   at UserQuery.add_Handler(EventHandler`1 value)
   at UserQuery.add_DelegatingHandler(EventHandler`1 value), line 16
   at UserQuery.Main(), line 5

Regression?

No response

Known Workarounds

No response

Configuration

.NET 10 on Windows 11

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions