Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert generic parameter with 'where' type constraint not possible?

Tags:

c#

generics

I have a base class:

public abstract class DomainEventSubscriber<T> where T : DomainEvent
{
    public abstract void HandleEvent(T domainEvent);
    public Type SubscribedToEventType() { return typeof(T); }
}

And a class which stores the DomainEventSubscriber references:

public class DomainEventPublisher
{
    private List<DomainEventSubscriber<DomainEvent>> subscribers;

    public void Subscribe<T>(DomainEventSubscriber<T> subscriber)
        where T : DomainEvent
    {
        DomainEventSubscriber<DomainEvent> eventSubscriber;
        eventSubscriber = (DomainEventSubscriber<DomainEvent>)subscriber;

        if (!this.Publishing)
        {
            this.subscribers.Add(eventSubscriber);
        }
    }
}

Even though the Subscribe method type is constrained, I cannot convert from DomainEventSubscriber<T> subscriber where T : DomainEvent to DomainEventSubscriber<DomainEvent>:

eventSubscriber = (DomainEventSubscriber<DomainEvent>)subscriber;

How would I go about performing this conversion, or am I setting myself up for a nasty code smell?

like image 731
morleyc Avatar asked May 05 '13 18:05

morleyc


People also ask

Which of the following generic constraints restricts the generic type parameter to an object of the class?

Value type constraint If we declare the generic class using the following code then we will get a compile-time error if we try to substitute a reference type for the type parameter.

Can a generic class have multiple constraints?

Multiple interface constraints can be specified. The constraining interface can also be generic.

Where is generic type constraint?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function. Constraints can specify interfaces, base classes, or require a generic type to be a reference, value, or unmanaged type.

Is it possible to inherit from a generic type?

An attribute cannot inherit from a generic class, nor can a generic class inherit from an attribute.


1 Answers

Covariance

You need an interface with a covariant type parameter T to be able to cast it to a base type of T. For example, IEnumerable<out T> is such an interface. Note the out keyword, which means T is covariant and can therefore only appear in output positions (e.g. as return values and getters). Because of covariance, you can cast an IEnumerable<Dolphin> to IEnumerable<Mammal>: an enumerable sequence of dolphins is surely also an enumerable sequence of mammals.

Contravariance

However, you cannot make DomainEventSubscriber<T> an interface IDomainEventSubscriber<out T> as T then appears in the input position of HandleEvent. You could make it an interface IDomainEventSubscriber<in T>.

Note the in keyword, which means T is contravariant and can only appear in input positions (e.g. as method parameters). For example, IEqualityComparer<in T> is such an interface. Because of contravariance, you can cast an IEqualityComparer<Mammal> to IEqualityComparer<Dolphin>: if it can compare mammals, then surely it can compare dolphins as they are mammals.

But this also does not solve your problem since you can cast a contravariant type parameter only to a more derived type, and you want to cast it to base type.


Solution

I advise you to create a non-generic IDomainEventSubscriber interface and derive your current class from that:

public interface IDomainEventSubscriber
{
    void HandleEvent(DomainEvent domainEvent);
    Type SubscribedToEventType();
}

public abstract class DomainEventSubscriber<T> : IDomainEventSubscriber
    where T : DomainEvent
{
    void IDomainEventSubscriber.HandleEvent(DomainEvent domainEvent)
    {
        if (domainEvent.GetType() != SubscribedToEventType())
            throw new ArgumentException("domainEvent");

        HandleEvent((T)domainEvent);
    }

    public abstract void HandleEvent(T domainEvent);

    public Type SubscribedToEventType() { return typeof(T); }
}

And then use the IDomainEventSubscriber internally instead of DomainEventSubscriber<DomainEvent>:

public class DomainEventPublisher
{
    private List<IDomainEventSubscriber> subscribers;

    public void Subscribe<T>(DomainEventSubscriber<T> subscriber)
        where T : DomainEvent
    {
        if (!this.Publishing)
        {
            this.subscribers.Add(eventSubscriber);
        }
    }
}
like image 123
Daniel A.A. Pelsmaeker Avatar answered Nov 10 '22 20:11

Daniel A.A. Pelsmaeker