通用接口上的扩展方法,其中 T 是可枚举的 <K>

Extension methods on a generic interface where T is Enumerable<K>

我正在实现一个流畅的参数断言库,其中重点是编译时的强类型检查。 Intellisense 应该只显示可用于断言类型的方法和扩展。

我在为 IEnumerable 创建扩展时遇到了解析正确类型参数的问题。

库中的想法是您可以在任何类型上调用 ThrowIf(或 ThrowIfNot),这将 return 您是 IAssertion 类型的断言实例:

public static IAssertion<T> ThrowIf<T>(this T t)
{
    return new IfAssertion<T>(t);
}

现在我想检查 IEnumerable 是否包含特定项目。将有两个重载,其中一个将类型 T 的对象作为参数,另一个将函数用于执行评估:

public static T1 Contains<T1, T2>(this IAssertion<T1> assertion, T2 item)
    where T1 : IEnumerable<T2>
{
    // assertion logic
    return assertion.Value;
}

public static T1 Contains<T1, T2>(this IAssertion<T1> assertion, Func<T2, bool> func)
    where T1 : IEnumerable<T2>
{
    // assertion logic
    return assertion.Value;
}

使用重载获取实际类型的实例时一切正常。但是后一个带有函数编译器的函数无法正确推断类型参数,除非进行了转换:

var list = new List<string>();
list.ThrowIf().Contains("foo"); // compiles
list.ThrowIf().Contains((string s) => false); // compiles
list.ThrowIf().Contains(s => false); // does not compile

有什么方法可以让编译器满意而无需对函数参数进行转换?

可以从此处找到更多实施细节: https://bitbucket.org/mikalkai/argument-assertions/overview

免责声明:此答案仅在 IAssertion 可以 covariant 时才有效。

假设 IAssertion 是协变的,那么 Contains 方法不一定需要两个泛型类型参数 T1T2。相反,您直接在界面中指定 IEnumerable 并仅使用一个泛型类型参数,如下所示:

public static IEnumerable<T> Contains<T>(this IAssertion<IEnumerable<T>> assertion, T item)
{
  // assertion logic
  return assertion.Value;
}

public static IEnumerable<T> Contains<T>(this IAssertion<IEnumerable<T>> assertion, Func<T, bool> func)
{
  // assertion logic
  return assertion.Value;
}

然后你可以像这样使用contains方法:

var list = new List<string>();
list.ThrowIf().Contains("foo"); // compiles
list.ThrowIf().Contains((string s) => false); // compiles
list.ThrowIf().Contains(s => false); // compiles now too