0

I have the following two interfaces

public class IMessage
{
}


public interface IListener<TMessage>  where TMessage : IMessage
{
    void ProcessMessage(TMessage message);
}

I need a specific Listener To implement IListener<TMessage> that takes a specific type of message

public class DeleteEmployeeMessage : IMessage
{
    public int EmployeeId { get; set; }
}

Now I implement my Listener like so

public class DeleteEmployeeListener : IListener<DeleteEmployeeMessage>
{
    public void ProcessMessage(DeleteEmployeeMessage)
    {
        // CODE HERE
    }

}

Now I want to create an object of DeleteEmployeeListener and cast it to generic interface type;

IListener<IMessage> listenerInterfaceObj;
DeleteEmployeeListener concreteMessageListener = new DeleteEmployeeListener();

listenerInterfaceObj = (IListener<IMessage>) concreteMessageListener; // this line crashes at runtime

I get the following runtime error

System.InvalidCastException: 'Unable to cast object of type 'DeleteEmployeeListener' to type 'IListener`1[IMessage]'.'

Why is that? Why can't I cast a Cat to an Animal?

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
fahadash
  • 3,133
  • 1
  • 30
  • 59
  • 1
    Should you be able to cast a `List` to `IList`? How might that relate to your situation? – mjwills Jul 02 '20 at 01:22
  • 1
    You're not casting `Cat` to `Animal`. `DeleteEmployeeListener` implements `IListener`. It does not implement `IListener`. That's why you can't cast `DeleteEmployeeMessage` to `IListener` – Enigmativity Jul 02 '20 at 01:22
  • 4
    `listenerInterfaceObj` is an object that can process all kinds of `IMessage`s, because its declared type is `IListener`, but `concreteMessageListener` can only process `DeleteEmployeeMessage`. Therefore, `IListener` is not a kind of `IListener`. – Sweeper Jul 02 '20 at 01:23
  • 1
    https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/creating-variant-generic-interfaces may be worth a read (may or may not be useful in your context). – mjwills Jul 02 '20 at 01:24
  • If you had `IFruit`, `Apple : IFruit`, and `Banana: IFruit`, then if you could cast `List` to `List` you'd be able to add a `Banana` to a list that can only take `Apple`. Boom. Run-time error. – Enigmativity Jul 02 '20 at 01:26
  • Just as an aside, I presume you mean `public interface IMessage` (not `public class IMessage`) – Jonathan Willcock Jul 02 '20 at 01:32

1 Answers1

0

Consider we introduce messages and listeners for creating employees:

public class CreateMessage : IMessage {
    public int EmployeeId { get; set; }
}

public class CreateEmployeeListener : IListener<CreateMessage> {
    public void ProcessMessage(CreateMessage message) {
    }
}

// Later...
var listener = (IListener<IMessage>) new DeleteEmployeeListener();
IMessage createMessage = new CreateMessage();
listener.ProcessMessage(createMessage); // What happens here?

Although createMessage is an IMessage, DeleteEmployeeListener can't handle a CreateMessage.

In the case of your Ilistener interface, the generic type is contravariant, meaning the "input" types are generic. This means you can pass a more specific type, but not less.

More info here: https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

Evan Trimboli
  • 29,900
  • 6
  • 45
  • 66