Reputation: 2431
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?
Upvotes: 7
Views: 1156
Reputation: 4693
There're good answers for the solution, here I'm point out a little bit for the simple reason why you cannot make it work.
The generic base classes can be inherited, however, with different type parameters are just different classes. Consider List<int>
and List<String>
. Although they both have the generic definition of List`1
, but they take different type parameter; none of them are the base class of the other. What your method does, would be like the following:
public void Subscribe<T>(List<T> subscriber) where T: struct {
var eventSubscriber=(List<int>)subscriber;
// ...
}
this would not compile, and even you change declaration for compile-able as:
public void Subscribe<T>(List<T> subscriber) where T: struct {
var eventSubscriber=(List<int>)(subscriber as object);
// ...
}
and pass an instance of List<byte>
would be invalid cast at runtime.
Thus even you've specified the constraints, it just doesn't work.
Beside the existing answers, an alternative way is define a base class(can also be abstract) for the generic class, and make your Subscribe
take the base class. However, this would require that you move the abstract method to the base class and change the method signature as a generic method; so it might not be applicable for you.
For knowing the type variance, and assignment ability, you might want to have a look of my Q&A style question:
How to find the minimum covariant type for best fit between two types?
You can use it to get some information about the types, for debugging, and I wish that would be helpful to you for understanding about types.
Upvotes: 3
Reputation: 50306
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);
}
}
}
Upvotes: 5
Reputation: 109567
Here's a slightly different take using an interface:
public class DomainEvent
{
}
// The 'in' isn't actually needed to make this work, but it can be added anyway:
public interface IDomainEventSubscriber<in T> where T: DomainEvent
{
void HandleEvent(T domainEvent);
Type SubscribedToEventType();
}
public abstract class DomainEventSubscriber<T>: IDomainEventSubscriber<T> where T: DomainEvent
{
public abstract void HandleEvent(T domainEvent);
public Type SubscribedToEventType()
{
return typeof(T);
}
}
public class DomainEventPublisher
{
private List<DomainEventSubscriber<DomainEvent>> subscribers;
public void Subscribe<T>(IDomainEventSubscriber<T> subscriber)
where T: DomainEvent
{
DomainEventSubscriber<DomainEvent> eventSubscriber;
eventSubscriber = (DomainEventSubscriber<DomainEvent>)subscriber;
if (!this.Publishing)
{
this.subscribers.Add(eventSubscriber);
}
}
}
Upvotes: 1