2

This code correctly returns one row:

_loadedAssemblies.ForEach(x =>
{
    foundTypes.AddRange(from t in x.GetTypes()
                        where t.GetInterfaces().Contains(typeof(TInterface))
                     && t.BaseType.Name.LeftOf('`') == baseClass.Name.LeftOf('`')
                        select t);
}

However, when I use PredicateBuilder, I get zero rows:

var compiledPredicate = CompiledPredicate<TInterface>();

_loadedAssemblies.ForEach(x =>
{
    foundTypes.AddRange(from t in x.GetTypes()
                        where compiledPredicate.Invoke(typeof(TInterface))
                        select t);
}

    private static Func<Type, bool> CompiledPredicate<T>() where T : class
    {
        // True means all records will be returned if no other predicates are applied.
        var predicate = PredicateBuilder.True<Type>();

        // Get types that implement the interface (T).
        predicate = predicate.And(t => t.GetInterfaces().Contains(typeof(T)));

        // If the config file includes filtering by base class, then filter by it.
        if (!string.IsNullOrWhiteSpace(_baseClass))
        {
            Type baseClass = Type.GetType(_baseClass);
            predicate = predicate.And(t => t.BaseType.Name.LeftOf('`') == baseClass.Name.LeftOf('`'));
        }

        return predicate.Compile();
}

Someone suggested creating a copy of my loop variable, but I tried that and I still get zero rows. I'm not sure why using PredicateBuilder returns no rows. Any idea what I'm missing?

Community
  • 1
  • 1
Bob Horn
  • 33,387
  • 34
  • 113
  • 219
  • Why are you using `PredicateBuilder` at all if you're using linq to objects? It's really only useful for dealing with expressions. – Servy Dec 10 '13 at 15:59
  • @Servy Because I don't know any better. Is using linq to objects the problem here? – Bob Horn Dec 10 '13 at 16:01
  • There are simply much easier ways when using linq to objects. You're using a sledgehammer to drive a nail. – Servy Dec 10 '13 at 16:02
  • Okay, good to know. So what's a better approach? – Bob Horn Dec 10 '13 at 16:03
  • I'm not sure why, but doing this works: `foundTypes.AddRange(x.GetTypes().AsQueryable().Where(compiledPredicate));` – Bob Horn Dec 10 '13 at 16:14
  • What is being passed to each call of the predicate in your original code? What is being passed to each call of the predicate in your second example. That's the difference. – Servy Dec 10 '13 at 17:04
  • In the second example, `Where()` is an extension method on `IQueryable`. So I would guess that the predicate gets a concrete `T`. In the first example (linq to objects), since I'm calling `Invoke()` on the predicate, and using `typeof(TInterface)` as the argument, then the predicate is only getting the type and not a concrete instance. And that was the problem. I would guess that the first example would work had I done this instead: `where compiledPredicate.Invoke(t)`. – Bob Horn Dec 10 '13 at 17:42
  • Correct. Your change had nothing at all to do with the fact that you were using `AsQueryable`. In the fist case you're passing in a hard coded type to each call of your predicate, in the second you're passing the given item from the sequence. Had you removed the `AsQueryable` and used the `Enumerable` `Where` it would also work, or had you passed the current item, rather than a hard coded type, when invoking it that would also work. – Servy Dec 10 '13 at 17:46
  • Yeah, I didn't see that issue until you made me think about it that way. If you want to use your comments as an answer, I'll accept. Thanks for the help. – Bob Horn Dec 10 '13 at 17:50

1 Answers1

1

The change that you mentioned in comments (foundTypes.AddRange(x.GetTypes().AsQueryable().Where(compiledPredicate));) had nothing at all to do with the fact that you were using AsQueryable. In the first case you're passing in a hard coded type to each call of your predicate, in the second you're passing the given item from the sequence. Had you removed the AsQueryable and used Enumerable.Where it would also work, or had you passed the current item, rather than a hard coded type, when invoking it that would also work.

So you can simply do:

foundTypes.AddRange(x.GetTypes().Where(compiledPredicate));

Also, when creating the predicate, there's no need to do as much work as you're doing. Expressions take a fair amount of extra work to deal with. With linq to objects you only need to deal with delegates, which are much less finicky.

private static Func<Type, bool> CompiledPredicate<T>() where T : class
{
    Func<Type, bool> predicate = t => t.GetInterfaces().Contains(typeof(T));

    if (!string.IsNullOrWhiteSpace(_baseClass))
    {
        Type baseClass = Type.GetType(_baseClass);
        return t => predicate(t) &&
            t.BaseType.Name.LeftOf('`') == baseClass.Name.LeftOf('`');
    }

    return predicate;
}
Servy
  • 202,030
  • 26
  • 332
  • 449