我正在实现一个fluent builder模式,它要求在静态扩展方法中接受可枚举项并迭代其内容,同时对可枚举项的内容应用函子。如中所示(不是实际的代码,只是一个示例):

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;
}

这对于可枚举性非常有效,但对于继承的类型/接口则失败。比如说:
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)

例外情况是:
IValidator<IList<Guid>>不包含“each”的定义
并且没有扩展方法“each”接受类型为的第一个参数
IValidator<IList<Guid>>可以找到(您是否缺少
指令还是程序集引用?
我的问题是关于IValidator<T>的继承链,更具体地说,它的泛型类型参数T。为什么类型IValidator<IEnumerable<T>>不能从IValidator<IList<T>>分配?我想不出在什么情况下IList<T>不是IEnumerable<T>(同样的T)。
将泛型参数约束为T : IEnumerable<R>确实有效,但这需要两个类型参数(TR),如果可能的话,我希望避免这两个类型参数。
有什么想法吗?更好的解决方案?谢谢。

最佳答案

这是因为IValidator<T>接口的定义。我敢打赌是这样的:

public interface IValidator<T>

你真正想要的是:
public interface IValidator<out T>

这将使您的接口covariant,这意味着您可以将IValidator<T2>的实现分配给IValidator<T>,假设T2来自T
在本例中,IList<T>IEnumerable<T>派生,因此您应该能够将T声明为协变。但是,这取决于IValidator<T>上的方法以及它们是如何暴露的。
也就是说,如果IValidator<T>上的方法将Tin的实例作为接口上任何方法的参数,那么就不能将接口声明为协变的。
如果是这样的话,那么您应该能够理解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;
}

这将表明T应该从IEnumerable<TValue>派生。

08-26 16:30