I'm trying to implement a strategy pattern to allow me to allow me to apply some "benefit" to an "account". In the code below, I can't add my implementation of an interface to a dictionary expecting the interface. I think it's some kind of contravariance problem, but it feels like I should be able to do this:
EDIT:
Since the answer seems to be that it's just not possible, any suggestions on how to achieve what I'm going for here?
void Main()
{
var provider = new BenefitStrategyProvider();
var freeBenefit = new FreeBenefit();
var strategy = provider.GetStrategy(freeBenefit);
strategy.ApplyBenefit(freeBenefit, new Account());
}
public class BenefitStrategyProvider
{
private Dictionary<Type, IBenefitStrategy<BenefitBase>> _strategies = new Dictionary<Type, IBenefitStrategy<BenefitBase>>();
public BenefitStrategyProvider()
{
/* Why can't I add this? */
_strategies.Add(typeof(FreeBenefit), new FreeBenefitStrategy());
}
public IBenefitStrategy<BenefitBase> GetStrategy(BenefitBase benefit)
{
return _strategies[benefit.GetType()];
}
}
public class Account {}
public abstract class BenefitBase
{
public string BenefitName {get;set;}
}
public class FreeBenefit : BenefitBase {}
public interface IBenefitStrategy<T> where T: BenefitBase
{
void ApplyBenefit(T benefit, Account account);
}
public class FreeBenefitStrategy : IBenefitStrategy<FreeBenefit>
{
public void ApplyBenefit(FreeBenefit benefit, Account account)
{
Console.WriteLine("Free Benefit applied");
}
}
EDITED - The formatting engine had removed everything in <angled brackets>
, which made it rather impossible to understand. Sorry if this was confusing!
FreeBenefitStrategy
implements IBenefitStrategy<FreeBenefit>
. It can apply only FreeBenefits
, not any other kind of benefit. It is not an IBenefitStrategy<BenefitBase>
, so you can't put it in a collection of those. Logically, IBenefiteStrategy
could be contravariant in BenefitBase
, but this doesn't help you here - an IBenefitStrategy<BenefitBase>
claims to be able to apply all kinds of benefits, so an IBenefitStrategy<BenefitBase>
is-an IBenefitStrategy<FreeBenefit>
, but the converse is not true - an IBenefitStrategy<FreeBenefit>
cannot apply any BenefitBase
.
I don't think there's any way to have a heterogenous collection like you want without using type-casting. If you think about it, there's no method that you can invoke on both an IBenefitStrategy<FreeBenefit>
and an IBenefitStrategy<ExpensiveBenefit>
beyond those that they share from object, so it makes sense that a variable of type object is the only thing that can point to either. If you want to keep them in the same dictionary, you'll need to make it a Dictionary<Type, object>
. You could change GetStrategy
to be generic and apply appropriate type-casting, but do be careful when looking up your dictionary - think what will happen if the object passed in is of a sub-class of FreeBenefit
.
See: Covariance and Contravariance FAQ
How can I create variant generic interfaces and delegates myself? The out keyword marks a type parameter as covariant, and the in keyword marks it as contravariant. The two most important rules to remember: You can mark a generic type parameter as covariant if it is used only as a method return type and is not used as a type of formal method parameters. And vice versa, you can mark a type as contravariant if it is used only as a type of formal method parameters and not used as a method return type.
You need to add out T to your interface:
public interface IBenefitStrategy<in T>
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