【问题标题】:Extension methods on base generic interfaces基本通用接口上的扩展方法
【发布时间】:2012-08-31 13:35:49
【问题描述】:

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

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&lt;IList&lt;Guid&gt;&gt; 不包含“每个”的定义 并且没有扩展方法“每个”接受类型的第一个参数 IValidator&lt;IList&lt;Guid&gt;&gt; 可以找到(您是否缺少使用 指令还是程序集引用?

我的问题是关于IValidator&lt;T&gt; 的继承链,更具体地说,是它的泛型类型参数T。为什么类型 IValidator&lt;IEnumerable&lt;T&gt;&gt; 不能从 IValidator&lt;IList&lt;T&gt;&gt; 分配?我想不出在哪个IList&lt;T&gt; 不是IEnumerable&lt;T&gt; 的情况下(给定相同的T)。

将泛型参数限制为 T : IEnumerable&lt;R&gt; 确实有效,但这需要两个类型参数(TR),如果可能的话,我想避免。

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

【问题讨论】:

  • 我认为这可能是一个协变和逆变问题。让我看看我能不能得到一些示例代码来看看是不是这样。
  • 即使你解决了你当前的问题,方法的意义何在?您没有使用传入的函子的布尔结果,因此除非函子改变对象,否则最终结果不会发生任何变化,如果它确实改变了它,那么......嗯......它不应该,因为 Linq 是专为序列查询而非突变而设计。
  • @Servy - 这只是说明我意图的示例,而不是实际代码。我将其保留为 Func&lt;T, bool&gt;,因为在下面的示例中编写调用 lambda 所需的代码比 Action&lt;T&gt; 少。我有点懒惰。 :-)
  • IEnumerable(.Net 4+)是协变的,但 IList 不是,因为后者同时接受输入和输出参数。
  • 好吧,我没有时间写一些代码。我的建议是当你使用IValidator&lt;T&gt; 时尝试IValidator&lt;out T&gt;IValidator&lt;in T&gt;,我忘了是哪一个。您也可以只使用 var guids = validator.Each..... 这可能会有所帮助,但它不会消除您的设计问题。

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


【解决方案1】:

这是由于您的IValidator&lt;T&gt; 接口的定义。我打赌它是这样的:

public interface IValidator<T>

你真正想要的是:

public interface IValidator<out T>

这将使您的接口covariant,这意味着您可以将IValidator&lt;T2&gt; 的实现分配给IValidator&lt;T&gt;,假设T2 派生自T

在这种情况下,IList&lt;T&gt; 派生自 IEnumerable&lt;T&gt;,因此您应该能够将 T 声明为协变。但是,这取决于IValidator&lt;T&gt; 上的方法以及它们的公开方式。

也就是说,如果IValidator&lt;T&gt; 上的方法将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&lt;TValue&gt;

【讨论】:

  • 在上面的@JasonEvans 评论之后,我记得我完全忘记了协/逆变。你是对的。使接口协变解决了这个问题。感谢您的意见。
【解决方案2】:

两个答案:

  1. where T: IEnumerable&lt;T&gt; 是完全合法的,它可以解决您的问题并且不需要第二个泛型类型。

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

协方差允许您用派生程度更高的类型替换泛型参数,而泛型参数在进行赋值等时需要派生程度较低的类型。

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

【讨论】:

    猜你喜欢
    • 2016-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多