基础通用接口的扩展方法

时间:2012-08-31 13:35:49

标签: c# .net generics extension-methods ienumerable

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

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>>不包含“每个”的定义   没有扩展方法'每个'接受第一个类型的参数   可以找到IValidator<IList<Guid>>(你错过了使用吗?   指令或汇编参考?

我的问题是关于IValidator<T>的继承链,更具体地说,是它的泛型类型参数T。为什么类型IValidator<IEnumerable<T>>无法从IValidator<IList<T>>分配?我无法想到哪个IList<T>不是IEnumerable<T>(给定相同的T)。

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

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

2 个答案:

答案 0 :(得分:6)

这是由于您的IValidator<T>界面的定义。我认为这有点像:

public interface IValidator<T>

你真正想要的是:

public interface IValidator<out T>

这会使您的界面covariant,这意味着您可以将IValidator<T2>的实施分配给IValidator<T>,假设T2来自T。< / p>

在这种情况下,IList<T>来自IEnumerable<T>,因此能够将T声明为协变。但是,这取决于IValidator<T>上的方法及其暴露方式。

即,如果IValidator<T>上的方法将T的实例作为参数接受到接口上的任何方法,那么您将无法将该接口声明为协变。< / p>

如果是这种情况,那么您应该能够摆脱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>

答案 1 :(得分:2)

两个答案:

  1. where T: IEnumerable<T>可以解决您的问题并且不需要第二种通用​​类型是完全合法的。

  2. IValidator<IEnumerable<T>>无法从IValidator<IList<T>>分配,因为您的界面不是变体。从v4开始,C#支持接口差异,但您必须在接口定义中明确要求它。为此,请参阅out Generic Modifier

  3. 协方差允许您将更多派生类型替换为在执行赋值时期望较少派生类型的泛型参数等。

    请注意,您需要确保您的界面确实是变体安全的。这就是为什么在C#4之前不可能的原因,因为它不是总是是真的它可以工作;某些接口是安全协变的,有些是安全逆变的(您只能将 less 派生类型替换为预期类型)。这一切都取决于你在做什么。