3

I have a problem to cast to generic parent, this is my codes

    public interface IEvent
    {
    }

    public interface IEventHandler<TEvent> where TEvent : IEvent
    {
        Task Handle(TEvent evt);
    }

    public class PersonCreatedEvent : IEvent
    {
        public int Id { get; set; }
    }

    public class PersonCreatedEventHandler : IEventHandler<PersonCreatedEvent>
    {
        public async Task Handle(PersonCreatedEvent evt)
        {
            Console.WriteLine("done");
        }
    }

And my code for casting is this:

    var handler = new PersonCreatedEventHandler();

    // exception occurred on this line 
    var cast = (IEventHandler<IEvent>)handler;

But I got exception when I want to cast instance.

Unable to cast object of type 'TestProject.ClientApi.PersonCreatedEventHandler' to type 'TestProject.ClientApi.IEventHandler`1[ElearnoInstitute.Endpoint.ClientApi.IEvent]'.

Why I get this exception? and how can resolve this problem.

Mahdi Radi
  • 429
  • 2
  • 10
  • 30
  • 1
    Just because `PersonCreatedEventHandler` is `IEventHandler` it doesn't make `IEventHandler` an `IEventHandler` – Tim Schmelter May 25 '21 at 17:12
  • I understand, but `PersonCreatedEvent` inherit from `IEvent` and I want to know, is there way to cast to `IEventHandler` – Mahdi Radi May 25 '21 at 17:16
  • 1
    Short answer: no. These types are simply not compatible. Take a moment to think about how you would (successfully) call `cast.Handle` -- you have stated you would like to be able to plug in any `IEvent`, but `PersonCreatedEventHandler` can only deal with `PersonCreatedEvent` instances. – Jeroen Mostert May 25 '21 at 17:28
  • Note that you can do something like `Func x = evt => handler.Handle((PersonCreatedEvent) evt)` -- the resulting delegate is compatible with the signature of `IEventHandler.Handle`, and you could implement this as a class, but of course things will fail at runtime now if the passed in `IEvent` is not a `PersonCreatedEvent`. Somewhere, someone has to check. – Jeroen Mostert May 25 '21 at 17:34

1 Answers1

3

That cast operation is forbidden because it can cause inconsistency problems. This is best explained with an example.

If we declare this class:

public class TestCreatedEvent : IEvent {

}

If your code were to be valid, we could do something like this:

var handler = new PersonCreatedEventHandler();
var cast = (IEventHandler<IEvent>)handler;
cast.Handle(new TestCreatedEvent());

We are passing an instance of something that's not a PersonCreatedEvent to the handler, that is a IEventHandler<PersonCreatedEvent> but not a IEventHandler<TestCreatedEvent>. The C# compiler gives the error to prevent that you can pass instances of another descendent type.

You can't cast types with generics to the same type but the generic being a super type. Another good example would be having a List<B>, B extending A, and having another class C also extending A. If you could cast List<B> to List<A>, you could add C instances to the casted List<A>, but originally that list is of type List<B>. Therefore, the result would be that you have C instances in a List<B>, which is a clear inconsistency.

JMSilla
  • 1,326
  • 10
  • 17
  • 2
    For me it's always best explained with the [unsafe giraffes](https://stackoverflow.com/questions/2033912/c-sharp-variance-problem-assigning-listderived-as-listbase) sample. `IList giraffes = new List(); IList animals = giraffes; animals.Add(new Lion());`. The assignment is not allowed for obvious reasons, just if `giraffes` was an array instead of a list, because you can't modify arrays. – Tim Schmelter May 25 '21 at 17:21
  • @TimSchmelter that's also a good example. In Java, the behaviour is also the same with generics. – JMSilla May 25 '21 at 17:26