How can I implement NotOfType<T> in LINQ that has a nice calling syntax?
Asked Answered
B

7

23

I'm trying to come up with an implementation for NotOfType, which has a readable call syntax. NotOfType should be the complement to OfType<T> and would consequently yield all elements that are not of type T

My goal was to implement a method which would be called just like OfType<T>, like in the last line of this snippet:

public abstract class Animal {}
public class Monkey : Animal {}
public class Giraffe : Animal {}
public class Lion : Animal {}

var monkey = new Monkey();
var giraffe = new Giraffe();
var lion = new Lion();

IEnumerable<Animal> animals = new Animal[] { monkey, giraffe, lion };

IEnumerable<Animal> fewerAnimals = animals.NotOfType<Giraffe>();

However, I can not come up with an implementation that supports that specific calling syntax.

This is what I've tried so far:

public static class EnumerableExtensions
{
    public static IEnumerable<T> NotOfType<T>(this IEnumerable<T> sequence, Type type)
    {
        return sequence.Where(x => x.GetType() != type);
    }

    public static IEnumerable<T> NotOfType<T, TExclude>(this IEnumerable<T> sequence)
    {
        return sequence.Where(x => !(x is TExclude));
    }
}

Calling these methods would look like this:

// Animal is inferred
IEnumerable<Animal> fewerAnimals = animals.NotOfType(typeof(Giraffe));

and

// Not all types could be inferred, so I have to state all types explicitly
IEnumerable<Animal> fewerAnimals = animals.NotOfType<Animal, Giraffe>();

I think that there are major drawbacks with the style of both of these calls. The first one suffers from a redundant "of type/type of" construct, and the second one just doesn't make sense (do I want a list of animals that are neither Animals nor Giraffes?).

So, is there a way to accomplish what I want? If not, could it be possible in future versions of the language? (I'm thinking that maybe one day we will have named type arguments, or that we only need to explicitly supply type arguments that can't be inferred?)

Or am I just being silly?

Bodoni answered 30/12, 2010 at 1:10 Comment(5)
I only write framework/shared code like this if I feel it is going to get a decent amount of reuse. How often do you need a method like this, and does it justify the cost (time spent thinking about the design difficulties)?Mina
Your two versions do substantially different things.Indefatigable
@Lette: The first one will also exclude inherited types.Indefatigable
@Merlyn: I'm off the clock, so no actual cost. But yes, there's probably not much reuse to be found here. Consider it an experiment, for now.Bodoni
@SLaks: Nice catch. I hadn't tested it with inheritance hierarchies deeper than two levels, but now I will. Thanks. However, that does not have any impact the essence of my question, which is more about method signature than implementation.Bodoni
I
11

How about

animals.NotOf(typeof(Giraffe));

Alternatively, you can split the generic parameters across two methods:

animals.NotOf().Type<Giraffe>();

public static NotOfHolder<TSource> NotOf<TSource>(this IEnumerable<TSource> source);

public class NotOfHolder<TSource> : IHideObjectMembers {
    public IEnumerable<TSource> NotOf<TNot>();
}

Also, you need to decide whether to also exclude inherited types.

Indefatigable answered 30/12, 2010 at 1:15 Comment(2)
I wrote a blog post explaining in greater detail: blog.slaks.net/2010/12/partial-type-inference-in-net.htmlIndefatigable
Finally, I've concluded that your solution will be best for my specific scenario. I'm taking into account the fact that the code will be used in automated acceptance tests, and they may at some time be read by people who might not be full-time developers. Having a call that is strongly typed all the way will make it easier to keep subsequent code more (human-)readable. Thanks.Bodoni
C
35

I am not sure why you don't just say:

animals.Where(x => !(x is Giraffe));

This seems perfectly readable to me. It is certainly more straight-forward to me than animals.NotOfType<Animal, Giraffe>() which would confuse me if I came across it... the first would never confuse me since it is immediately readable.

If you wanted a fluent interface, I suppose you could also do something like this with an extension method predicate on Object:

animals.Where(x => x.NotOfType<Giraffe>())
Collard answered 30/12, 2010 at 3:21 Comment(1)
Embarrassing that I spent good 10 minutes wondering why the ! didn't work in my case... Your answer made me realize that I forgot about the brackets...Caniff
I
11

How about

animals.NotOf(typeof(Giraffe));

Alternatively, you can split the generic parameters across two methods:

animals.NotOf().Type<Giraffe>();

public static NotOfHolder<TSource> NotOf<TSource>(this IEnumerable<TSource> source);

public class NotOfHolder<TSource> : IHideObjectMembers {
    public IEnumerable<TSource> NotOf<TNot>();
}

Also, you need to decide whether to also exclude inherited types.

Indefatigable answered 30/12, 2010 at 1:15 Comment(2)
I wrote a blog post explaining in greater detail: blog.slaks.net/2010/12/partial-type-inference-in-net.htmlIndefatigable
Finally, I've concluded that your solution will be best for my specific scenario. I'm taking into account the fact that the code will be used in automated acceptance tests, and they may at some time be read by people who might not be full-time developers. Having a call that is strongly typed all the way will make it easier to keep subsequent code more (human-)readable. Thanks.Bodoni
R
5

This might seem like a strange suggestion, but what about an extension method on plain old IEnumerable? This would mirror the signature of OfType<T>, and it would also eliminate the issue of the redundant <T, TExclude> type parameters.

I would also argue that if you have a strongly-typed sequence already, there is very little reason for a special NotOfType<T> method; it seems a lot more potentially useful (in my mind) to exclude a specific type from a sequence of arbitrary type... or let me put it this way: if you're dealing with an IEnumerable<T>, it's trivial to call Where(x => !(x is T)); the usefulness of a method like NotOfType<T> becomes more questionable in this case.

Ruthenic answered 30/12, 2010 at 2:21 Comment(4)
Interesting idea regarding the non-generic IEnumerable. It will make the calling code look exactly as I wanted. Obviously, it will not return a strongly-typed sequence, which is a minor inconvenience.Bodoni
Regarding the 2nd paragraph: One could argue that the usefulness of NotOfType<T> matches that of OfType<T>... (It's interesting to try to apply your arguments to OfType<T> instead.) My argument is only about code readability. FYI, in the actual business case I have a sequence of Event elements that are of different subtypes, and in one specific scenario the requirements called for a listing of all events, except one particular type. This experiment was all about finding a way to express that requirement in easily readable code.Bodoni
@Lette: OfType<T>() is different because it changes the type of the sequence. NotOfType cannot change the type of the sequence.Indefatigable
@SLaks: You're both absolutely right. I'm a little embarrased right now over the fact that I totally missed that the OfType method's source is IEnumerable and not IEnumerable<T>... Still a good discussion, though.Bodoni
N
4

I had a similar problem, and came across this question whilst looking for an answer.

I instead settled for the following calling syntax:

var fewerAnimals = animals.Except(animals.OfType<Giraffe>());

It has the disadvantage that it enumerates the collection twice (so cannot be used with an infinite series or a collection where enumeration has side-effects that should not be repeated), but the advantage that no new helper function is required, and the meaning is clear.

In my actual use case, I also ended up adding a .Where(...) after the .OfType<Giraffe>() (giraffes also included unless they meet a particular exclusion condition that only makes sense for giraffes)

Necropsy answered 28/2, 2017 at 16:51 Comment(0)
E
2

If you're going to make a method for inference, you want to infer all the way. That requires an example of each type:

public static class ExtMethods
{
    public static IEnumerable<T> NotOfType<T, U>(this IEnumerable<T> source)
    {
        return source.Where(t => !(t is U));
    }
      // helper method for type inference by example
    public static IEnumerable<T> NotOfSameType<T, U>(
      this IEnumerable<T> source,
      U example)
    {
        return source.NotOfType<T, U>();
    }
}

called by

List<ValueType> items = new List<ValueType>() { 1, 1.0m, 1.0 };
IEnumerable<ValueType> result = items.NotOfSameType(2);
Etna answered 30/12, 2010 at 3:53 Comment(0)
J
0

I've just tried this and it works...

public static IEnumerable<TResult> NotOfType<TExclude, TResult>(this IEnumerable<TResult> sequence)
    => sequence.Where(x => !(x is TExclude));

Am I missing something?

Jen answered 25/5, 2016 at 21:15 Comment(1)
It's not that it doesn't work. My objection is that you'd need to specify both types (TSource, TResult) for the generic call and that just looks weird for this specific call.Bodoni
R
0

You might consider this

public static IEnumerable NotOfType<TResult>(this IEnumerable source)
{
    Type type = typeof(Type);

    foreach (var item in source)
    {
       if (type != item.GetType())
        {
            yield return item;
        }
    }
}
Reikoreilly answered 30/6, 2016 at 13:41 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.