tags:

views:

127

answers:

4

In the code there exists exactly one type that implements IResourceConverter. That's what the two following linq statements are looking for. The former does not find it. The latter does. However they both are equivalent syntaxes (or at least should be!).

Linq Statement 1:

List<Type> toInstantiate = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(assembly => assembly.GetTypes())
    .Where(type => typeof(IResourceConverter).IsAssignableFrom(type) 
        && type != typeof(IResourceConverter))
    .ToList();

This returns 0 results.

Linq Statement 2:

I have left the linq intact except for the where clause, which I broke out and did the equivalent with a foreach loop

List<Type> toInstantiate = new List<Type>();            
List<Type> allTypes = AppDomain.CurrentDomain.GetAssemblies()
    .SelectMany(assembly => assembly.GetTypes())
    .ToList();

foreach (Type t in allTypes)
{
    if (typeof(IResourceConverter).IsAssignableFrom(t) 
        && t != typeof(IResourceConverter))
    toInstantiate.Add(t);
}

In this case toInstantiate has 1 result ... exactly what I would have expected.

Any explanation for this weird behavior?

A: 

I am not really a LINQ expert, but I can hazard a guess; this appears similar to a problem I encounted in Hibernate (a Java ORM tool):

In Hibernate, a Collection property can be set to be initialized lazily. When the property is not initialized, Hibernate uses bytecode instrumentation to generate proxies, by subclassing Parent and adding its own behavior to do the lazy loading operation. If I have classes like:

class Test {
 Collection<Parent> getEntities() //lazy
}

class Parent extends Child {
}

class Child {
}

I can call getEntities() to return a collection of Parent objects. Since my getEntities is marked lazy, the objects I get back are automatically generated subclasses of Parent. Even if one of the items in the collection represents a Child, a check like: "myEntity instanceof Child" will not work, because the actual object is only a proxy to the Child, not a real Child object.

I understand that LINQ is a querying mechanism, which can be used for data access or on objects. In your case, maybe the "type" object in your where clause is some kind of querying proxy to the actual Type object, similar to the case described above, so that isAssignable() determines that the proxy object doesn't implement IResourceConverter?

RMorrisey
A: 

Make sure that the expression below actually evaluates to true (i.e. take it out of the LINQ statement, and evaluate it directly against the type itself):

bool doesImplIface = typeof(IResourceConverter).IsAssignableFrom(type) && type != typeof(IResourceConverter);

The only reason the Where clause would filter the result out is because the expression is not evaluating true. Running the above line of code should clearly indicate whether the expression you are trying to execute evaluates the way you think it does, or not. If not, tweak it until you get the appropriate behavior, and put the new expression in the LINQ statement.

jrista
The thing is, both expressions are identical, the only difference is one is coming from a list, and the other a lazily evaluated enumerator.
KeeperOfTheSoul
I would expect that the lazy enumerator and the list would behave the same. It shouldn't matter if you evaluate in parts or as a composite whole...the result should be the same. At least, as I understand IEnumerable<T> and LINQ it should.
jrista
+2  A: 

This also appears to produce the correct result but I'm not sure why.

List<Type> allTypes = AppDomain.CurrentDomain
    .GetAssemblies().SelectMany(assembly => assembly.GetTypes())
    .ToList();

List<Type> toInstantiate = allTypes
    .Where(type => typeof(IList).IsAssignableFrom(type) && type != typeof(IList))
    .ToList();

The only difference between the foreach, this query and the original query is the original query is lazily evaluated. Why this would make a difference since types are static I'm unsure.

KeeperOfTheSoul
The difference is that this way, you cause all dependent assemblies to load first, and then check for `IsAssignableFrom`. When there's just one LINQ query, by the moment of the check, the only dependent assemblies loaded are those of assemblies we've already iterated through. By "dependent" here I mean assemblies containing base types / implemented interfaces of types we're iterating through. Even so, I still don't see why this should make any difference.
Pavel Minaev
+1  A: 

Run the following program, and compare files a.txt and b.txt using a diff tool.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;
using System.Reflection;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = Foo().OrderBy(t => t.FullName).Select(t => t.FullName);
            var y = Bar().OrderBy(t => t.FullName).Select(t => t.FullName);

            File.WriteAllLines("a.txt", x.ToArray());
            File.WriteAllLines("b.txt", y.ToArray());
            Console.ReadKey();
        }

        private static List<Assembly> Foo()
        {
            List<Type> toInstantiate = AppDomain.CurrentDomain
                .GetAssemblies().SelectMany(assembly => assembly.GetTypes())
                .ToList();

            return toInstantiate.Select(t => t.Assembly).Distinct().ToList();
        }

        private static List<Assembly> Bar()
        {
            List<Type> toInstantiate = new List<Type>();
            List<Type> allTypes = AppDomain.CurrentDomain
                .GetAssemblies().SelectMany(assembly => assembly.GetTypes())
                .ToList();

            foreach (Type t in allTypes)
            {
                toInstantiate.Add(t);
            }

            return toInstantiate.Select(t => t.Assembly).Distinct().ToList();
        }
    }
}

I'm noticing a big difference in the assemblies the two pieces of code can see. Namely, the second function, Bar can see assemblies the linq based one cannot.

More interestingly is if I reverse the execution order, now Foo can see assemblies the other one cannot, ie the exact reverse.

Lastly, if I run the first query twice, the output is identical, so:

    Foo, Foo, Bar
    Foo, Bar, Foo
    Bar, Bar, Foo
    Bar, Foo, Bar

All produce the same result.

So my only assumption is some assemblies are being loaded by one query that the other query does not cause to load.

KeeperOfTheSoul
Very good stuff - thanks for all work on this Keeper.
Daniel