Search code examples
reflectionmono.cecil

BaseType.Resolve() yielding null for base types in different assembly


I have a method to get all members of a type using mono.cecil, all the way through the type hierarchy, but I've found that often the following statement returns null:

(asmType has type 'TypeDefinition')
TypeDefinition baseType = asmType.BaseType.Resolve();

Prior to this statement I've checked that asmType.BaseType is not null (and I know that the base type exists). I think it tends to return null when the base type and derived type are in different assemblies. Both assemblies are being examined by mono.cecil, so I know it's capable of finding and handling both, at least individually.

Is there some way to nudge cecil to correctly resolve the base type?

UPDATE: If I add this code, it works, but it is not practical as a general solution:

if (baseType == null)
{
    var test = AssemblyDefinition.ReadAssembly("<hard-coded explicit path>");
    baseType = test.MainModule.GetType(asmType.BaseType.FullName);
}

UPDATE 2:

I tried using:

AssemblyDefinition.ReadAssembly(asmType.BaseType.Module.FileName)

But 'FileName' for some reason is the file name of 'asmType' (derived type) and not the base type ?

UPDATE 3:

I was able to work-around the problem by using my own cache for AssemblyDefinition which only records non-null values. The mono.cecil code adds possibly null values into its cache in the DefaultAssemblyResolver class, even though its possible to resolve the assembly successfully at a later point after that first attempt. The cecil 'Resolve' method in the DefaultAssemblyResolver class will never resolve an assembly after an initial attempt produces null since it will always later return the cached null value.


Solution

  • I found a better work-around - but this just looks in already-read assemblies and looks for the base class there. Very strange that mono.cecil doesn't do this consistently:

    private static readonly Dictionary<string, AssemblyDefinition> _AssemblyDefinitions = new();
    
    //whenever 'AssemblyDefinition.ReadAssembly' is called, add to _AssemblyDefinitions if non-null, with the key being the AssemblyDefinition FullName
    
    private static TypeDefinition TypeReferenceToTypeDefinition(TypeReference typeReference)
    {
        TypeDefinition typeDefinition = typeReference.Resolve();
    
        //mono.cecil may possibly return a null value from its cache in the DefaultAssemblyResolver.Resolve method:
        if (typeDefinition == null)
        {
            if (_AssemblyDefinitions.TryGetValue(typeReference.Scope.ToString(), out AssemblyDefinition foundAssemblyDefinition))
                typeDefinition = foundAssemblyDefinition.MainModule.GetType(typeReference.FullName);
        }
    
        return typeDefinition;
    }
    

    Debugging mono.cecil shows that in the 'DefaultAssemblyResolver' class, the 'cache' dictionary sometimes has an item added with the AssemblyDefinition being null, so for my specific case, 'Resolve' returned the null item from the cache. My own version of 'cache' had a non-null item (I don't add null values to my cache, so if ReadAssembly returns null once it's not doomed forever to be null). I haven't figured out why mono.cecil adds null items to its cache.