Please read the entire question. I have a unique situation with several constraints that I'd like to solve.
In my code I have an expression tree which is compiled to a Predicate<System.Type>
. My goal is to load an assembly without locking it (it is the output assembly of the project, being rebuilt constantly), apply this predicate on the list of its types and get back a list of the resulting type names:
// this is what I need:
return assembly.GetTypes().Where(t => predicate(t)).Select(t => t.FullName);
This assembly should be loaded in another appdomain, because I want to unload it as soon as I get the information I require.
Here is where it gets tricky. There are several problems I'm facing:
If I load the assembly in another appdomain and simply return an array of all of its types, so that I could apply the predicate back in my main appdomain, as soon as the Types are marshalled back to my main appdomain I get a FileNotFoundException
, stating that the this assembly is not found. This makes sence, because the assembly is only being loaded in another appdomain I created. Loading it also in the main appdomain will defeat the purpose.
If, alternatively, I try to pass the predicate into the other appdomain, to apply it there and get back an array of strings (full type name), I get a SerializationException: "Cannot serialize delegates over unmanaged function pointers, dynamic methods or methods outside the delegate creator's assembly."
, because the predicate is a Dynamic Method (compiled from an expression tree).
Loading it in the primary appdomain would have none of this problems, but since it's impossible to unload a loaded assembly without unloading the entire appdomain, as soon as the assembly would change (after rebuild), any attempt to load an assembly with the same name would result in an exception.
Context:
I am building a plugin for ReSharper called Agent Mulder. The idea behind the plugin is to analyze DI/IoC Container registrations in your solution, and help ReSharper figure out the usage of types registered via a DI Container (you can watch a short video of how it works here).
For the most part, analyzing container registration is straightforward - I only have to detect enough information to know which concrete types are affected. In this example with Castle Windsor: Component.For<IFoo>().ImplementedBy<Foo>()
the resulting type is obvious, so is AllTypes.FromThisAssembly().BasedOn<IFoo>()
- giving me just enough information to guestimate the concrete types that will be affected by this line. However, consider this registration in Castle Windsor:
container.Register(Classes
.FromAssemblyInDirectory(new AssemblyFilter(".").FilterByName(an => an.Name.StartsWith("Ploeh.Samples.Booking")))
.Where(t => !(t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dispatcher<>)))
.WithServiceAllInterfaces());
(source)
Here the information depends on a predicate that will only be evaluated at runtime.
Since all I can do is statically analyze this, I have in my hand the ReSharper's AST (called PSI in ReSharper) representation of the lambda expression from the Where
clause. I can convert this AST into a LINQ expression tree, then compile it into a delegate.
My idea was is to load the output assembly (determined by the FromAssembly*
descriptor) via reflection, and apply this delegate on the assembly's types in order to get the type names (I only need the names). This will have to be re-evaluated every time the assembly changes, too (I'm not concerned at this point about performance).
In conclusion, unless someone could recommend a better way of determine the types affected by the predicate, I'd like to know how to do this with reflection (unfortunately I hadn't considered other metadata readers, because I'd have to somehow convert the lambda expression AST to a predicate of different data type, and I don't know if there exists a 1-to-1 conversion).
Thank you for reading. This question will have a 500 point bounty when it becomes available.
Expression
was serializable, you could create thePredicate
delegate as anExpression
, and pass that over the appdomain boundary, and compile it in the 'remote' appdomain. But sadly, that is not that case :( – Ratepayer