I'm seeing very weird behavior when trying to add two methods with different signatures (that are covariant one to the other). It throws an ArgumentException: Incompatible Delegate Types
when I try to add the second method.
public class SomeClass { } // Just a class that inherits from object
public interface GenericInterface<out T> { // An interface with a covariant parameter T
event System.Action<T> doSomethingWithT;
}
public interface SpecificInterface : GenericInterface<SomeClass> { } // A more specific interface where T = SomeClass
public class ImpClass: SpecificInterface { // An implementation of the more specific interface
public event System.Action<SomeClass> doSomethingWithT;
}
Basically a simple generic interface where the generic param is covariant, a child interface that assigns a type to the generic, and an implementation of the child interface.
This is the code that throws the exception:
protected void Start() {
ImpClass impObj = new ImpClass();
GenericInterface<object> genericObj = impObj; // assignment possible because interface is covariant
impObj.doSomethingWithT += DoSomethingSpecific;
genericObj.doSomethingWithT += DoSomething; // this line throws an exception
}
protected void DoSomething(object o) { }
protected void DoSomethingSpecific(SomeClass o) { }
Now the code compiles fine, and adding only the more specific or only the more general methods each work fine separately, but if I try to add both, I get the exception.
Doesn't make sense. Any idea why? And any solutions?
I still haven't found an explanation as to why this happens, but I did find a workaround that will let you do this. You'd have to implement accessors to the event and save the delegates in a separate list or hashset instead of using the builtin event implementation.
public class ImpClass: SpecificInterface { // An implementation of the more specific interface
public event System.Action<SomeClass> doSomethingWithT {
add { delegateSubs.Add(value); }
remove { delegateSubs.Remove(value); }
}
protected HashSet<System.Action<SomeClass>> delegateSubs = new HashSet<System.Action<SomeClass>>();
}
That way you can add/remove delegates of multiple base types of T without any problem. The drawback is of course you'd have to do that for every class that implements the interface, but it guarrantees that whenever you use the events of these classes regardless of T, it will work and not throw an exception.