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?
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.
Multiple interface constraints can be specified. The constraining interface can also be generic.
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.
An attribute cannot inherit from a generic class, nor can a generic class inherit from an attribute.
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.
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.
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);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With