Adventures with IL Merge
A recent addition to AutoMapper is the feature where I can map directly to and from interfaces. You don’t need a concrete type to do this, AutoMapper will create an implementation for you at runtime, with default property behavior. To do so, I needed to use the slick LinFu project, with its dynamic proxy abilities. This required me to add a reference to an assembly that most folks don’t have, as everything else AutoMapper uses is straight up .NET.
I don’t really care about referencing and distributing multiple assemblies, but some folks do. The solution here is to use another slick tool, ILMerge. Check out Daniel Cazzzzzulino’s blog for a great example of using it. In my case, I wanted to use ILMerge to merge LinFu and AutoMapper into one assembly, but make all of LinFu internal. End users will just reference AutoMapper, with little or no knowledge that I’m really using LinFu as well.
Things worked great until someone tried to use the merged assembly, and got this fun error:
System.TypeLoadException : Access is denied: 'LinFu.DynamicProxy.ProxyDummy'. at AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext context) at AutoMapper.MappingEngine.Map(Object source, Type sourceType, Type destinationType) at AutoMapper.MappingEngine.Map[TSource,TDestination](TSource source) at AutoMapper.Mapper.Map[TSource,TDestination](TSource source) C:devautomapper-trunksrcAutoMapperSamplesInterfaces.cs(45,0): at AutoMapperSamples.Interfaces.MappingToInterfaces.Example() --TypeLoadException at System.Reflection.Emit.TypeBuilder._TermCreateClass(Int32 handle, Module module) at System.Reflection.Emit.TypeBuilder.CreateTypeNoLock() at System.Reflection.Emit.TypeBuilder.CreateType() at LinFu.DynamicProxy.ProxyFactory.CreateUncachedProxyType(Type baseInterfaces, Type baseType) at LinFu.DynamicProxy.ProxyFactory.CreateProxyType(Type baseType, Type baseInterfaces) at LinFu.DynamicProxy.ProxyFactory.CreateProxy(Type instanceType, IInterceptor interceptor, Type baseInterfaces) at AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.CreateObject(Type type) at AutoMapper.Mappers.TypeMapMapper.Map(ResolutionContext context, IMappingEngineRunner mapper) at AutoMapper.MappingEngine.AutoMapper.IMappingEngineRunner.Map(ResolutionContext context)
Blah blah blah, all this means is that I made too much internal. To figure out what was going on here, I set a debug point on a unit test that used project references (instead of referencing the merged assembly). What I found was that at runtime, my runtime type implements my destination type as well as a LinFu type, “LinFu.DynamicProxy.IProxy”. This IProxy type exposes another LinFu type, “LinFu.DynamicProxy.IInterceptor”. Although most of LinFu’s types are internal in my ILMerge’d assembly, I still need these two to be public.
To do this, I created an excludes file for ILMerge.exe, which lets me specify different types to leave as-is:
ILMerge.exe uses regular expressions to match each line, so I made these types use exact matching (otherwise, I’d get anything that just started with IProxy to be public). With this exclude file in place, I do have a couple of types I need to make public, which means they’ll show up in IntelliSense, but my tests now pass.