jornb87
jornb87

Reputation: 1461

Strongly typed LINQ extension methods

I have noticed that all of the Linq extension methods Where, Any, Take etc. all return IEnumerable. For my application it would be a lot nicer if these returned an instance of the same type as the one I am calling on.

So for instance, if I call myList.Where(...) on a LinkedList, I would like to get a LinkedList back, on a List i'd like a List back, etc.

This becomes more annoying when I have a type like

class MyType
{
    public string MyField;
}

class MyCollection : List<MyType>
{
    // Some useful stuff in here
}

in which case I would like to be able to do

MyCollection source = .......
MyCollection filtered = source.Where(obj => obj.MyField != null);

Sadly, this won't work out of the box. However, I have come close, using the following code

public static ListType Filtered<ListType, ElementType>(this ListType @this, Predicate<ElementType> filter) where ListType : class, ICollection<ElementType>, new()
{
    // Arguments checks omitted

    // Create return instance
    var filtered = new ListType();

    // Apply filter for each element
    foreach (var item in @this)
    {
        if (filter(item))
            filtered.Add(item);
    }

    return filtered;
}

I can do (Notice the stronly typed delegate)

MyCollection source = .......
MyCollection filtered = source.Filtered((MyType obj) => obj.MyField != null);

So my question is: Why do I need the second generic argument in Filter<ListType, ElementType>, when I specify where ListType : ICollection<ElementType>? I understand I cannot use any generic types in where that I haven't added to the method declaration, but why is this? Is there a nicer way of doing this that I have overlooked?

Upvotes: 1

Views: 253

Answers (1)

Jon Skeet
Jon Skeet

Reputation: 1499730

Well, you need two type parameters because you've got two types involved: the collection type, and the element type. There's no way of saying, it's got to implement IFoo<T> for some kind of T, but we don't care what that T is.

You're also using both types elsewhere in the declaration: ListType as the return type, and ElementType in the predicate declaration. (This method declaration would be easier to understand if you'd followed .NET naming conventions and called them something like TCollection and TElement, by the way.)

You're also using both types within the method body: item is statically typed to be ElementType, and filtered is statically typed to be ListType.

It's hard to see how you would express all of this without both type parameters. Given that type inference lets you call the method without being explicit, it's not clear to me why this is a problem...

Upvotes: 3

Related Questions