基础通用接口的扩展方法

lso*_*ira 5 .net c# generics ienumerable extension-methods

我正在实现一个流畅的构建器模式,它需要在静态扩展方法中接受可枚举并迭代其内容,同时将一个仿函数应用于可枚举的内容.如(不是实际代码,只是一个例子):

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;
}
Run Code Online (Sandbox Code Playgroud)

这对于枚举非常有效,但对于继承的类型/接口则失败.让我们说:

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)
Run Code Online (Sandbox Code Playgroud)

例外是:

IValidator<IList<Guid>>不包含'Each'的定义,也没有扩展方法'each'接受类型的第一个参数 IValidator<IList<Guid>>可以找到(你是否缺少using指令或汇编引用?

我的问题是关于继承链IValidator<T>,更具体地说,是它的泛型类型参数T.为什么类型IValidator<IEnumerable<T>>不可分配IValidator<IList<T>>?没有任何情况我可以想到哪个IList<T>不是IEnumerable<T>(给定相同T).

约束泛型参数T : IEnumerable<R>确实有效,但是如果可能的话,这需要两个我想避免的类型参数(TR).

有什么想法吗?好的解决方案 谢谢.

cas*_*One 6

这是由于您的IValidator<T>界面的定义.我敢打赌它是这样的:

public interface IValidator<T>
Run Code Online (Sandbox Code Playgroud)

你真正想要的是:

public interface IValidator<out T>
Run Code Online (Sandbox Code Playgroud)

这将使您的接口协变,这意味着您可以分配IValidator<T2>to 的实现IValidator<T>,假设T2派生自T.

在这种情况下,IList<T>派生自IEnumerable<T>,所以你应该能够声明T为协变.但是,这取决于IValidator<T>它们的暴露方法和方式.

也就是说,如果您有方法将接口的IValidator<T>实例T作为接口上的任何方法的参数,那么您将无法将接口声明为协变.

如果是这种情况,那么你应该能够摆脱这个定义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;
}
Run Code Online (Sandbox Code Playgroud)

这表明T应该来自IEnumerable<TValue>.