How can an extension method be bound to a generic class when the type argument is IEnumerable <T>?

As a hobby project (and dive deeper into generics / extension methods), I am writing a parameter checking library!

I have a model called Argument that describes a parameter and looks like this:

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; } } 

When a check for a parameter begins, an instance of this object is created, and individual checks are performed by calling extension methods (containing the actual logic) that disconnect from it.

One of these extension methods checks that the collection contains at least one item and currently looks like this:

 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; } 

But it does not work. If I, say, wrote this unit test:

 [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 does not appear in Intellisense, and I get this compilation error:

'Validation.Argument<System.Collections.Generic.List<int>>' does not contain a definition for "HasItems" and no extension method "HasItems" that takes the first argument of the type 'Validation.Argument<System.Collections.Generic.List<int>>' , can be found (are you missing the using directive or assembly references?)

And if I try to pass the value directly to the extension method, for example:

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

I get this:

The best overloaded method match for 'Validation.CollectionTypeExtensions.HasItems<int>(Validation.Argument<System.Collections.Generic.IEnumerable<int>>)' has some invalid arguments

Based on my research, what I need for this is called β€œvariance” and applies to interfaces and delegates, but not to classes (i.e. all classes are invariant.)

However, this may work differently. Anyone who comes to mind should rewrite it to go directly to T, for example:

 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; } 

.. But this does not work either because it requires the TElement to be explicitly specified when the method is called. I could also return to using the universal IEnumerable interface in a type constraint, but then I would either have to find a way to force IEnumerable to IEnumerable (which would require knowing that T in this context), or duplicate Any () functionality to check for any any elements, as well as another extension method (All) that would be very very dirty, so I would rather avoid it.

So, ultimately, I guess my question is: how do I get an extension method for a proper installation?

+6
source share
2 answers

Does this work for you? It seems a bit cludgy, but it really works.

 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; } 
+2
source

I believe that you really need an IArgument interface covariant in 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); } 
0
source

All Articles