Reputation: 975
Can anyone explain why the conversion in the return in the switch statement doesn't compile in .net 4? I've updated the example to be more accurate to my situation. The factory itself isn't generic actually.
Even casting "as BaseProductProcessor" does not work IF I'm passing in a base Product (that's actually a StandardProduct). Now if I explicitly pass a StandardProduct type to the factory, then it's ok - but what I have defined is a Product type in all calling methods anyway :|
How to get around this?
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace testing
{
[TestClass]
public class Test
{
[TestMethod]//fails
public void TestFactoryMethodWithBaseTypePassed()
{
Product product = new testing.StandardProduct();
var pp = new testing.ProductProcessorFactory().Create(product);
Assert.IsNotNull(pp);//fails because T coming into create wasn't the derived type
}
[TestMethod]//passes
public void TestFactoryMethodWithExactType()
{
var pp = new testing.ProductProcessorFactory().Create(new testing.StandardProduct());
Assert.IsNotNull(pp);
}
}
public abstract class BaseProductProcessor<T> where T : Product
{
public T Product { get; set; }
public BaseProductProcessor(T product)
{
Product = product;
}
}
public class StandardProductProcessor : BaseProductProcessor<StandardProduct>
{
public StandardProductProcessor(StandardProduct product)
: base(product)
{
}
}
public class ProductProcessorFactory
{
public ProductProcessorFactory()
{
}
public BaseProductProcessor<T> Create<T>(T product) where T : Product
{
switch (product.ProductType)
{
case ProductType.Standard:
var spp = new StandardProductProcessor(product as StandardProduct);
return spp as BaseProductProcessor<T>;//Nulls if T passed with a Product.. how to explicitly say T is a StandardProduct right here in the factory method so it's centralized?
}
return null;// spp as BaseProductProcessor<T>;
}
}
public class Product
{
public ProductType ProductType { get; set; }
}
public enum ProductType
{
Standard,
Special
}
public class StandardProduct : Product
{
}
}
Upvotes: 1
Views: 116
Reputation: 1364
Well, here you want to achieve covariance of template parameter. It is not possible with base classes, but it IS possible with intefaces. So, I suggest you to replace your abstract class BaseProductProcessor<T>
with interface:
public interface IBaseProductProcessor<out T> where T : Product // out marks argument as covariant
{
T Product { get; } // absense of setter is crusial here - otherwise you'll violate type safety
}
StandartProcessor:
public class StandardProductProcessor : IBaseProductProcessor<StandardProduct>
{
public StandardProductProcessor(StandardProduct product)
{
Product = product;
}
public StandardProduct Product { get; private set; }
}
and with this, just modify your factory function as following: public class ProductProcessorFactory { public ProductProcessorFactory() { }
public IBaseProductProcessor<T> Create<T>(T product) where T : Product
{
switch (product.ProductType)
{
case ProductType.Standard:
var spp = new StandardProductProcessor(product as StandardProduct);
return spp as IBaseProductProcessor<T>;//no more nulls!
}
return null;
}
}
With this modifications, both of your tests will pass.
If you want to learn more about covariance and contravariance(out and in keywords in C#), I recommend the excellent series in Eric Lippert's blog (start with the bottom ones)
Upvotes: 1
Reputation: 28737
That's because StandardProductProcessor
expects an object of type StandardProduct
.
At design time you only know that you have a Product
.
While every StandardProduct
is a Product
, that doesn't go the other way around. Not every Product
is a StandardProduct
, that's why you need to tell the compiler explicitly that you have a StandardProduct
Upvotes: 3