I noticed while trying to implement a generic that there was different behavior between a class with a generic that implements an interface vs a class with a generic that extends a Base class. With the interface, I can't call a function that takes an Enumerable of the interface type, but with the class everything works just fine. Here's an example
public interface IBarInterface
{
public int Value { get; set; }
}
public class FooInterface<TInterface> where TInterface : IBarInterface
{
private List<TInterface> _items;
public List<TInterface> Items => _items;
// Does not compile:
// Argument type 'System.Collections.Generic.List<TInterface>' is not assignable to parameter type 'System.Collections.Generic.IEnumerable<IBarInterface>'
public bool SomeValue => Processors.DoSomethingInterface(_items);
public FooInterface()
{
_items = new List<TInterface>();
}
}
public class BarClass
{
public int Value { get; set; }
}
public class FooClass<TClass> where TClass : BarClass
{
private List<TClass> _items;
public List<TClass> Items => _items;
// Does compile
public bool SomeValue => Processors.DoSomethingClass(_items);
public FooClass()
{
_items = new List<TClass>();
}
}
public static class Processors
{
public static bool DoSomethingInterface(IEnumerable<IBarInterface> items)
=> items.Count() % 2 == 0;
public static bool DoSomethingClass(IEnumerable<BarClass> items)
=> items.Count() % 2 == 0;
}
FooInterface
fails to compile, but FooBar
compiles just fine. Why is this the case?
The crucial difference between interfaces and classes in this case, is that structs can implement interfaces too! Covariance/contravariance conversions, like converting from IEnumerable<Subtype>
to IEnumerable<Supertype>
, is only available if Subtype
and Supertype
are both reference types.
In the case of FooClass<TClass>
, TClass
is constrained to be a subclass of BarClass
, so TClass
has to be a reference type.
In the case of FooInterface<TInterface>
, TInterface
is only constrained to be an implementation of IBarInterface
, so it can be struct
too. There is no guarantee that a covariance conversion is valid at DoSomethingInterface(_items)
.
So if you just ensure that TInterface
cannot be a value type,
where TInterface : class, IBarInterface
then the error will be gone.