views:

472

answers:

6

I want to write code that is decouple and clean, and I know that by programming to an interface instead of the implementation, my code will be more flexible and extensible. So, instead of writing methods like:

  bool IsProductAvailable(ProductTypeA product);

I write methods like:

  bool IsProductAvailable(IProduct product);

As long as my products implement IProduct:

  class ProductTypeA : IProduct

I should be OK. All is well until I start using generic collections. Since C# 3.0 doesn't support covariant and contravariant, even though both ProuctTypeA and ProductTypeB implements IProduct, you cannot put List in List. This is pretty troublesome because a lot of times I want to write something like:

bool AreProductsAvailable(List<IProduct> products);

So that I can check product avaialbility by writing:

List<ProductA> productsArrived = GetDataFromDataabase();
bool result = AreProductsAvailable(productsArrived);

And I want to write just one AreProductsAvailable() method that works with all IProduct collections.

I know that C# 4.0 is going to support covariant and contravariant, but I also realize that there other libraries that seemed to have the problem solved. For instance, I was trying out ILOG Gantt the gantt chart control, and found that they have a lot of collection intefaces that looks like this:

IActivityCollection
ILinkCollection

So it seems like their approach is wrapping the generic collection with an interface. So instead of "bool AreProductsAvailable(List products);", I can do:

bool AreProductsAvailable(IProductCollection products);

And then write some code so that IProductCollection takes whatever generic collection of IProduct, be it List or List.

However, I don't know how to write an IProductCollection interface that does that "magic". :-< (ashame) ....

Could someone shed me some light? This has been bugging me for so long, and I so wanted to do the "right thing". Well, thanks!

+2  A: 

How about AreProductsAvailable(IEnumerable<IProduct> products)?

Call it like this:

using System.Linq;

List<ProductTypeA> products = ...;
AreProductsAvailable(products.Cast<IProduct>());
Abraham Pinzur
This is definitely what Deecay is after.
Fadrian Sudaman
A: 

public bool AreProductsAvailable<T>(List<T> products) where T : IProduct { ... }

Lee
+5  A: 

Even with .NET 4.0, List<T> won't be variant, for two reasons:

  • It's a class, not an interface or a delegate
  • It uses T in both input and output positions (which is why IList<T> isn't variant either in .NET 4.0)

One common solution to this problem is to add another type parameter:

bool AreProductsAvailable<T>(IList<T> products) where T : IProduct

You can then iterate through the list, accessing each element as just an IProduct - but the caller can use any compatible list type.

Jon Skeet
A: 
Danny Varod
A: 

I was stuck with the exact same problem. I had

class Bar : IFin // IFin is interface { }

Passing List to a function that expected List proved to be a headache (cannot understand why.. after all Bar is implementing all in IFin!!).

I ended up using the List<>.ConvertAll with the convert function as below:

public static IFin ToIFin(Bar_bar) { return (IFin)_bar; }

I know I sometimes miss some very obvious things but to me jumping though that hoop looked stupid. I know this is not elegant and it does generate a complete list of IFin again but atleast the new list still holds my original Bar objects (that I did verify).

Jagbir
A: 

I probably wouldn't approach this problem in this fashion (though I realize that this may be an example, and not a real scenario...)

When using an interface, the consumer should not care about the specific implementation behind the interface. This abstraction is entirely the point of using interfaces.

Since the code called will need to know what type of product it is being queried about, it will need to know the implementor of the interface. Since part of the reason to use an interface is to hide this knowledge, it's a pretty good sign that you shouldn't use interfaces in this case.

A better idea might be to pass a ProductId or something similar. Now, you could have your interface include a GetProductId() method. But, if the callee really wants a ProductId, it's more decoupled to just give him what he wants, rather than something that he can use to get what he really wants.

kyoryu