I've been trying to create a generic event. Basically it should look like this:
namespace DelegateTest
{
class Program
{
static void Main(string[] args)
{
var lol = new SomeClass();
lol.SomeEvent += handler;
}
static void handler(object sender, SomeDerivedClass e)
{
}
}
class SomeClass
{
public delegate void SomeEventDelegate<in T>(object sender, T data);
public event SomeEventDelegate<ISomeInterface> SomeEvent;
}
interface ISomeInterface
{
}
class SomeDerivedClass : ISomeInterface
{
}
}
I want to allow the user to pass any delegate which's second parameter is derived from "ISomeInterface."
"in" specifies contra-variance, right? That means if the API is expecting something more general, you can pass it something more specific (in my base "ISomeInterface" would be general, and my "SomeDerivedClass" would be specific.) I am, however, being told my the compiler that "no overload for method handler matches DelegateTest.SomeClass.SomeEventDelegate."
I am wondering why this isn't working. What are the problems that would be caused if it was? Or am I missing something for it to work?
Thanks in advance!
One major annoyance with delegate contravariance is that while a delegate of type e.g.
Action<Fruit>
may be passed to a routine expecting anAction<Banana>
, an attempt to combine two delegates whose actual types areAction<Fruit>
andAction<Banana>
will fail *even if both delegates have "compile-time" typeAction<Banana>
. To get around this, I would suggest using a method like the following:Given a delegate and a delegate type, this method will check whether the type of the passed-in delegate precisely matches the specified type; if it doesn't, but the method(s) in the original delegate have the proper signatures for the specified type, a new delegate will be created with the necessary characteristics. Note that if on two separate occasions this function is passed delegates which are not of the proper type but which compare equal to each other, the delegates returned by this method will also compare equal to each other. Thus, if one has an event which is supposed to accept a delegate of type
Action<string>
, one could use the above method to convert e.g. a passed-inAction<object>
into a "real"Action<string>
before adding or removing it from the event.If one will be adding or subtracting a passed-in delegate from a field of the proper delegate type, type inference and Intellisense behavior may be improved if one uses the following methods:
These methods will appear as extension methods on types derived from
Delegate
, and will allow instances of such types to be added to or subtracted from variables or fields of suitable delegate types; such addition or subtraction will be done in thread-safe fashion, so it may be possible to use these methods in event add/remove methods without additional locking.Yes.
No. Delegate contravariance allows a delegate to reference a method with parameter types that are less derived than in the delegate type. For example, suppose
ISomeInterface
had a base interface:And suppose
handler
tookISomeBaseInterface
instead ofSomeDerivedClass
:Then
new SomeClass().SomeEvent += handler
would work.Here's why the original code isn't type safe: When
SomeClass
raisesSomeEvent
, it can potentially pass anything that implementsISomeInterface
as thedata
argument. For example, it could pass an instance ofSomeDerivedClass
, but it could also pass an instance ofIf you were able to register
void handler(object sender, SomeDerivedClass e)
with the event, that handler would wind up being invoked withSomeOtherDerivedClass
, which doesn't work.In summary, you can register event handlers that are more general than the event type, not event handlers that are more specific.
UPDATE: You commented:
I don't think C# lets you declare an
event
that works with arbitrary delegate types. Here's how you can write methods that add event handlers and invoke them: