5

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

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>>(您是否缺少 using 指令或程序集引用?

我的问题是关于继承链IValidator<T>,更具体地说,是关于它的泛型类型 arguments T。为什么 typeIValidator<IEnumerable<T>>不能分配 from IValidator<IList<T>>?我想不出哪种IList<T>情况不是IEnumerable<T>(给定相同的T)。

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

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

4

2 回答 2

6

这是由于您的IValidator<T>接口的定义。我打赌它是这样的:

public interface IValidator<T>

你真正想要的是:

public interface IValidator<out T>

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

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

也就是说,如果您有将 in 的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;
}

这将表明T应该从IEnumerable<TValue>.

于 2012-08-31T14:06:27.713 回答
2

两个答案:

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

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

协变允许您用派生更多的类型替换泛型参数,而泛型参数在进行赋值等时需要派生较少的类型。

请注意,确保您的界面确实是变体安全的,这取决于您。这就是为什么在 C# 4 之前它是不可能的,因为它并不总是有效的;有些接口是安全协变的,有些是安全逆变的(您只能用较少派生的类型替换预期的类型)。这完全取决于你在做什么。

于 2012-08-31T14:07:22.207 回答