Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enumerating through list of expressions to filter collection

I'm pushing the limits of my knowledge in c# and linq here, so please bear with me if I'm completely off with my example or understanding of linq, c#, generic types, lambda expressions,design patterns, etc.

I have a class that holds two collections, one is the collection to be filtered: IEnumberable<InstagramUser> and the second is the collection of expressions to filter on: IEnumerable<IInstagramFilter>.

public class InstagramDisplay {

    public IEnumerable<InstagramUser> instagramUsers;
    public IEnumerable<IInstagramFilter> instagramFilters; 

    public InstagramDisplay() {
        instagramUsers = new List<InstagramUser>();
        instagramFilters = new List<IInstagramFilter>();
    }

    public IEnumerable<InstagramUser> display() {
        instagramFilters.ToList().ForEach(x => instagramUsers.Where(x.filter(instagramUsers)));
        return instagramFilters;
    }
}

public interface IInstagramFilter {
    Expression<Func<T, bool>> filter<T>(IQueryable<T> source); 
}

I would have classes extend IInstagramFilter. Each IInstagramFilter class would have a property (or function - not sure what's best) that would return the lambda expression that would be applied to IEnumerable<InstagramUser> in the display() method.

public class UserFilter : IInstagramFilter {
    public Expression<Func<T, bool>> filter<T>(IQueryable<T> source) {
        //return some expression - but how?
    }
}

I'm struggling to understand a few things:

  1. How to set the expression for each IInstagramFilter class and then call it in the display() method?

  2. Each IInstagramFilter class would have a lambda that would be used to filter IEnumerable<InstagramUser> but since the Filter class has no knowledge of IEnumerable<InstagramUser> how would I create the appropriate lambda in the first place?

  3. I think this roughly follows the Decorator Pattern but perhaps there's a better design all together that I'm not aware of.

UPDATED CODE

Based on Olivier's answer this is what I have now. On the return for display() I'm getting the error when using .Where(filter)

The type arguments for method 'System.Linq.Enumerable.Where<TSource>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,bool>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

public class InstagramDisplay {

    public IEnumerable<InstagramUser> instagramUsers;
    public List<Expression<Func<InstagramUser, bool>>> instagramFilters; 

    public InstagramDisplay() {
        instagramUsers = new List<InstagramUser>();
        instagramFilters = new List<Expression<Func<InstagramUser, bool>>>();
    }

    public void addFilter(Expression<Func<InstagramUser, bool>> filter) {
        instagramFilters.Add(filter);
    }

    public IEnumerable<InstagramUser> display() {
        return instagramFilters.SelectMany(filter => instagramUsers.Where(filter)).Distinct(); //error on this line
    }
}
like image 678
bflemi3 Avatar asked Aug 23 '12 19:08

bflemi3


1 Answers

You have to decide whether you want to perfom some action or whether you want to return something. List<T>.ForEach() performs an action on each item but has a void return type and does not return anything.

This IInstagramFilter interface seems superfluous to me. You can declare a filter list like this

var userFilters = new List<Expression<Func<InstagramUser, bool>>>();
userFilters.Add(u => u.Name.StartsWith("A"));
userFilters.Add(u => u.Score >= 100);

If you have a source of users you can do something like this to all users returned by all filters

IQueryable<InstagramUser> usersSource = ...; 
// Or IEnumerable<InstagramUser> for LINQ to objects
// if you drop the Expression<> part.

var users = userFilters.SelectMany(f => usersSource.Where(f));

SelectMany flattens the nested enumerations. The example returns all users whos name starts with "A" or who have a score >= 100. This might return a user twice, therfore I would suggest to append a .Distinct()

var users = userFilters
    .SelectMany(f => usersSource.Where(f))
    .Distinct();
like image 130
Olivier Jacot-Descombes Avatar answered Nov 10 '22 10:11

Olivier Jacot-Descombes