当类型参数是IEnumerable <t>?</t>时,如何将扩展方法附加到泛型类

时间:2013-03-03 07:33:04

标签: c# .net generics compiler-errors extension-methods

作为一个爱好项目(让我更深入地了解泛型/扩展方法),我正在编写一个参数检查库!

我有一个名为Argument的模型,它描述了一个参数,如下所示:

public class Argument<T>
{
    internal Argument(string name, T value)
    {
        Name = name;
        Value = value;
    }

    public string Name { get; private set; }

    public T Value { get; private set; }
}

当参数验证开始时,将创建此对象的实例,并通过调用挂起它的扩展方法(包含实际逻辑)来执行各个验证。

一种此类扩展方法验证集合是否包含至少一个项目,目前看起来像这样:

public static Argument<IEnumerable<T>> HasItems<T>(this Argument<IEnumerable<T>> argument)
{
    if (!argument.Value.Any())
        throw Error.Generic(argument.Name, "Collection contains no items.");

    return argument;
}

但它似乎不起作用。如果我是,那么,写下这个单元测试:

[TestMethod]
public void TestMethod1()
{
    var argument = new List<int>() { 1, 2, 6, 3, -1, 5, 0 };

    Validate.Argument("argument", argument)
        .IsNotNull()
        .HasItems()
        .All(v => v.IsGreaterThan(0));
}

HasItems没有出现在Intellisense中,我得到了这个编译错误:

  

'Validation.Argument<System.Collections.Generic.List<int>>'不包含'HasItems'的定义,也没有扩展方法'HasItems'可以找到类型'Validation.Argument<System.Collections.Generic.List<int>>'的第一个参数(你是否缺少using指令或汇编引用? )

如果我尝试将值直接传递给扩展方法,就像这样:

CollectionTypeExtensions.HasItems(Validate.Argument("argument", argument));

我明白了:

  

'Validation.CollectionTypeExtensions.HasItems<int>(Validation.Argument<System.Collections.Generic.IEnumerable<int>>)'的最佳重载方法匹配有一些无效的参数

根据我的研究,我需要这个工作称为“方差”,并且适用于接口和委托,但不适用于类(即:所有类都是不变的。)

那说,它可能会以另一种方式运作。想到的是重写它直接转到T,就像这样:

public static Argument<T> HasItems<T, TElement>(this Argument<T> argument)
        where T : IEnumerable<TElement>
{
    if (!argument.Value.Any())
        throw Error.Generic(argument.Name, "Collection contains no items.");

    return argument;
}

..但这不起作用,因为它需要在调用方法时显式指定TElement。我也可以回退到在类型约束中使用非通用的IEnumerable接口,但是我必须找到一种方法将IEnumerable强制转换为IEnumerable(这需要知道该上下文中的T是什么),或者重复Any()的功能是为了测试任何项目的存在,还有另一种扩展方法(All),它会非常非常混乱,所以我宁愿避免使用它。

所以最终,我想我的问题是:如何让我的扩展方法正确附加?

2 个答案:

答案 0 :(得分:2)

这对你有用吗?看起来有点笨拙,但确实有效。

public static Argument<T> HasItems<T>(this Argument<T> argument) where T: IEnumerable
{
    if (!argument.Value.Cast<object>().Any())
    {
        throw Error.Generic(argument.Name, "Collection contains no items.");
    }

    return argument;
}

答案 1 :(得分:0)

我相信你真正想要的是一个界面IArgument,它在T上是协变的:

public static class Validate
{
    public static IArgument<T> Argument<T>(string name, T value)
    {
        return new Argument<T>(name, value);
    }
}

public interface IArgument<out T>
{
    string Name { get; }
    T Value { get; }
}

public class Argument<T> : IArgument<T>
{
    internal Argument(string name, T value)
    {
        Name = name;
        Value = value;
    }

    public string Name { get; private set; }

    public T Value { get; private set; }
}

public static class ExtensionMethods
{
    public static IArgument<T> IsNotNull<T>(this IArgument<T> argument)
    {
        return argument;
    }

    public static IArgument<IEnumerable<T>> HasItems<T>(this IArgument<IEnumerable<T>> argument)
    {
        return argument;
    }

    public static IArgument<IEnumerable<T>> All<T>(this IArgument<IEnumerable<T>> argument, Predicate<T> predicate)
    {
        return argument;
    }
}

[TestMethod]
public void TestMethod1()
{
    List<int> argument = new List<int>() { 1, 2, 6, 3, -1, 5, 0 };

    Validate.Argument("argument", argument)
        .IsNotNull()
        .HasItems()
        .All(v => v > 0);
}