Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extension methods on base generic interfaces

I'm implementing a fluent builder pattern that requires accepting enumerables in a static extension method and iterating through its contents, while applying a functor to the enumerable's contents. As in (not the actual code, just an illustration):

public static IValidator<IEnumerable<T>> Each<T>(
    this IValidator<IEnumerable<T>> enumerable, 
    Func<T, bool> action)
{
    foreach (T value in enumerable)
        action(value);

    return validator;
}

This works pretty well for enumerables but fails for inherited types/interfaces. Let's say:

IValidator<IEnumerable<Guid>> validator = ...;

IEnumerable<Guid> guids = ...;
validator.Each(guids, guid => guid != Guid.Empty);   // ok

IList<Guid> guids = ...;
validator.Each(guids, guid => guid != Guid.Empty);   // doesn't compile (see below)

The exception is:

IValidator<IList<Guid>> does not contain a definition for 'Each' and no extension method 'Each' accepting a first argument of type IValidator<IList<Guid>> could be found (are you missing a using directive or an assembly reference?

My question is about the inheritance chain of IValidator<T> and, more specifically, its generic type arguments T. Why is type IValidator<IEnumerable<T>> not assignable from IValidator<IList<T>>? There's no circumstance I can think of on which IList<T> is not an IEnumerable<T> (given the same T).

Constraining the generic argument to T : IEnumerable<R> does work but that requires two type arguments (T and R) which I'd like to avoid, if possible.

Any thoughts? Better solutions? Thanks.

like image 598
lsoliveira Avatar asked Aug 31 '12 13:08

lsoliveira


1 Answers

This is due to the definition of your IValidator<T> interface. I'm betting it's something along the lines of:

public interface IValidator<T>

What you really want is:

public interface IValidator<out T>

This will make your interface covariant, meaning that you can assign an implementation of IValidator<T2> to IValidator<T>, assuming that T2 derives from T.

In this case, IList<T> derives from IEnumerable<T>, so you should be able to declare T as covariant. However, this depends on the methods on IValidator<T> and how they are exposed.

Namely, if you have methods on IValidator<T> that take instances of T in as a parameter to any methods on the interface, then you won't be able to declare the interface as covariant.

If this is the case, then you should be able to get away with this definition of Each:

public static IValidator<T> Each<T, TValue>(
    this IValidator<T> enumerable, 
    Func<TValue, bool> action) where T : IEnumerable<TValue>
{
    foreach (TValue value in enumerable)
        action(value);

    return validator;
}

This will indicate that T should derive from IEnumerable<TValue>.

like image 197
casperOne Avatar answered Sep 20 '22 01:09

casperOne