10

I have simplified and reproduced my problem in the code below. The error that I am receiving is: Argument 1: cannot convert from 'System.Collections.ObjectModel.Collection' to 'System.Collections.ObjectModel.Collection

Some background for the design decisions that led to this issue: I have created a web service for dealing with "IFruit", but due to the nature of SOAP and endpoint bindings as I understand them I have created methods with explicit implementations of IFruit. On the business layer, creating a separate method for each specific implementation of fruit would firstly cause a lot of duplicate code, and secondly couple the business and service layer tightly enough that accepting new IFruit types in the web service later would also require changing my code in the business layer (adding another copy of redundant code). This design is one I have successfully implemented using Java in the past, but C# interfaces seem to be just different enough to throw me. Please advise.

public interface IFruit{
    public string TakeBite();
}

public Apple : IFruit{
    public string TakeBite(){
        return "tasty bite of apple";
    }
}

public void EatFruit(Collection<IFruit> fruits){
    foreach(var fruit in fruits){
        Console.WriteLine(fruit.TakeBite());
    }
}

public void EatApples(Collection<Apple> apples){
    this.EatFruit(apples);
}

4 Answers4

11

Try changing your EatFruit method to accept IEnumerable<IFruit>

public void EatFruit(IEnumerable<IFruit> fruits)
{
    foreach (var fruit in fruits)
    {
        Console.WriteLine(fruit.TakeBite());
    }
}

The interface IEnumerable<out T> supports covariance since it is marked with the out modifier. Collection<T> is not marked with such a modifier.

Kevin Aenmey
  • 13,259
  • 5
  • 46
  • 45
1

That's called covariance.
However, it's not possible with mutable collections.

Otherwise, EatFruit could write

fruits.Add(new Orange());

.Net 4 does support covariance for reas-only interfaces, so you can change it to IEnumerable<IFruit> and it will work.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
1

Simple solution :

public void EatApples(Collection<Apple> apples){
     this.EatFruit(
          new Collection<IFruit>(
              apples.Cast<IFruit>()
          )
     );
}

Better solution : use the covariance introduced in C# 4 : see Kevin's answer

Julien Ch.
  • 1,231
  • 9
  • 16
0

Aside from using IEnumerable you could type check the collection (bit horrid but it should work):

public void EatApples(Collection<IFruit> fruit)
    var col = new Collection<IFruit>();

    fruit.Where(x => x is Apple).ToList().ForEach(x => col.Add(x));

    this.EatFruit(col);
}

Though I'd use IEnumerable instead - this is just an option if you can't refactor :P

(it's as type safe as you make it!)

Charleh
  • 13,749
  • 3
  • 37
  • 57
  • That would do 2x work - create a List then add each to the Collection. Better to do: `foreach (var x in fruit.OfType()) { col.Add(x); }`. – Kevin Brock Jul 06 '12 at 19:44
  • Yeah true - nowhere did I say it would be performant (and I did mention horrid in there too!) :D – Charleh Jul 06 '12 at 19:45