6

趣味のプロジェクトとして (そしてジェネリック/拡張メソッドに深く没頭するために)、パラメーター チェック ライブラリを作成しています!

パラメータを記述する 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; }
}

パラメーターの検証が開始されると、このオブジェクトのインスタンスが作成され、個々の検証は、それからぶら下がっている拡張メソッド (実際のロジックを含む) を呼び出すことによって実行されます。

このような拡張メソッドの 1 つは、コレクションに少なくとも 1 つの項目が含まれていることを確認します。現在は次のようになっています。

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) があるため、むしろ避けたいと思います。

最終的に、私の質問は次のとおりだと思います:拡張メソッドを適切にアタッチするにはどうすればよいですか?

4

2 に答える 2

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;
}
于 2013-03-03T10:20:49.133 に答える
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);
}
于 2013-03-06T05:17:24.263 に答える