Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

As a hobby project (and to immerse myself more deeply in generics/extension methods), I'm 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 validation for a parameter begins, an instance of this object is created, and individual validations are performed by invoking extension methods (that contain the actual logic) that hang off of it.

One such extension method verifies that a 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 doesn't appear to work. If I were, say, to write 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 doesn't show up in Intellisense, and I get this compile error:

'Validation.Argument<System.Collections.Generic.List<int>>' does not contain a definition for 'HasItems' and no extension method 'HasItems' accepting a first argument of type 'Validation.Argument<System.Collections.Generic.List<int>>' could be found (are you missing a using directive or an assembly reference?)

And if I try passing the value directly into the extension method, like so:

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'd need for this to work is called "variance," and applies to interfaces and delegates, but not to classes (ie: all classes are invariant.)

That said, it might work another way. One that comes to mind is to rewrite it to go straight to T, like so:

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 that doesn't work either because it requires TElement to be specified explicitly when the method is invoked. I could also fall back to using the non-generic IEnumerable interface in the type constraint, but then I'd have to either find a way to coerce the IEnumerable into IEnumerable (which would require knowing what T is in that context), or duplicating the functionality of Any() in order to test for the existence of any items, and there's another extension method (All) that that would be very, very messy for, so I'd rather avoid it.

So ultimately, I guess my question is: how do I get my extension method to attach properly?

like image 809
William Scalf Avatar asked Mar 03 '13 07:03

William Scalf


People also ask

Can you add extension methods to an existing static class?

No. Extension methods require an instance of an object.

How can use generic extension method in C#?

Extension Methods is a new feature in C# 3.0 and the Common Language Runtime, which allows existing classes to use extra methods (extension methods) without being implemented in the same class or obtained by inheritance.

Can extension methods extend non static classes?

Actually I'm answering the question of why extension methods cannot work with static classes. The answer is because extension methods are compiled into static methods that cannot recieve a reference to a static class.

Can we define extension method for class which itself is a static class?

Extension methods are defined as static methods but are called by using instance method syntax. Their first parameter specifies which type the method operates on. The parameter is preceded by the this modifier.


1 Answers

Does this work for you? Seems a bit cludgy, but it does actually work.

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;
}
like image 97
Matthew Watson Avatar answered Nov 04 '22 23:11

Matthew Watson