Table of Contents

Member References

Next to metadata symbols defined in the current module (see Metadata Definitions), .NET modules can also reference metadata defined in external assemblies using the AssemblyRef, TypeRef and MemberRef tables.

Below an overview of how AsmResolver represents definitions and their corresponding reference type.

Definition Reference
AssemblyDefinition AssemblyReference
TypeDefinition TypeReference
MethodDefinition MemberReference with a MethodSignature
FieldDefinition MemberReference with a FieldSignature
Note

While there also exists both a ModuleDefinition and ModuleReference, these two are unrelated. ModuleDefinition defines a module in a .NET assembly manifest, while ModuleReference is used to reference native modules such as kernel32.dll or libc.so

Note

Prior to AsmResolver 6.0, it is required to explicitly import member descriptors (e.g., TypeDefinition, FieldDefinition and MethodDefinition, MemberReference etc.) that are not defined or alraedy imported in the same assembly (e.g., using the ImportWith method.). For AsmResolver 6.0 and newer, importing is done automatically by the builder at build-time. You will only need to explicit importing when custom behavior is required (e.g., when cloning metadata, see Member Cloning).

Resolution scopes

The root scope of any external reference is an IResolutionScope.

Typically, resolution scopes are references to external assemblies, which are represented by an AssemblyReference. All assemblies imported by a module are stored in ModuleDefinition::AssemblyReferences:

ModuleDefinition module = ...;
foreach (var assembly in module.AssemblyReferences)
    Console.WriteLine(assembly);

Creating a new AssemblyReference can be done using its constructor:

ModuleDefinition module = ...;
var systemConsole = new AssemblyReference(
    "System.Console", 
    new Version(8, 0, 0, 0)
);
Note

Prior to AsmResolver 6.0, AssemblyReferences need to be manually imported using ImportWith. For example:

var systemConsole = new AssemblyReference(
   "System.Console", 
   new Version(8, 0, 0, 0)
).ImportWith(module.DefaultImporter); // <-- Explicitly import the reference.

An AssemblyDefinition can also be turned into an AssemblyReference:

ModuleDefinition module = ...;
AssemblyDefinition otherAssembly = ...;
AssemblyReference otherAssemblyRef = otherAssembly.ToAssemblyReference();

ModuleDefinition also provides a default corlib assembly scope that the module targets:

ModuleDefinition module = ...;
var corlib = module.CorLibTypeFactory.CorLibScope;

This will typically return an AssemblyReference to either mscorlib, netstandard, System.Runtime or System.Private.Corlib, depending on the target runtime the module was compiled for. In case module is a corlib assembly itself, it will reference the ModuleDefinition itself instead.

Type and member references

Similar to assembly references, external types and members are represented using TypeReference and MemberReference.

To create new references, use the fluent factory methods CreateTypeReference and CreateMemberReference on any IResolutionScope or ITypeDescriptor. Below is an example of how to create a fully imported reference to void System.Console.WriteLine(string):

var method = factory.CorLibScope
    .CreateTypeReference("System", "Console")
    .CreateMemberReference("WriteLine", MethodSignature.CreateStatic(
        factory.Void, factory.String));

// importedMethod now references "void System.Console.WriteLine(string)"
Note

Prior to AsmResolver 6.0, TypeReferences and MemberReferences need to be manually imported using ImportWith. For example:

var method = factory.CorLibScope
   .CreateTypeReference("System", "Console")
   .CreateMemberReference("WriteLine", MethodSignature.CreateStatic(factory.Void, factory.String))
   .ImportWith(module.DefaultImporter); // Explicitly import the reference.

Generic type instantiations can be created using MakeGenericInstanceType:

ModuleDefinition module = ...

var factory = module.CorLibTypeFactory;
var importedMethod = factory.CorLibScope
    .CreateTypeReference("System.Collections.Generic", "List`1")
    .MakeGenericInstanceType(factory.Int32)
    .ToTypeDefOrRef()
    .CreateMemberReference("Add", MethodSignature.CreateInstance(
        factory.Void,
        new GenericParameterSignature(GenericParameterType.Type, 0)));

// importedMethod now references "System.Collections.Generic.List`1<System.Int32>.Add(!0)"

Similarly, generic method instantiations can be constructed using MakeGenericInstanceMethod:

ModuleDefinition module = ...

var factory = module.CorLibTypeFactory;
var importedMethod = factory.CorLibScope
    .CreateTypeReference("System", "Array")
    .CreateMemberReference("Empty", MethodSignature.CreateStatic(
        new GenericParameterSignature(GenericParameterType.Method, 0).MakeSzArrayType(), 1))
    .MakeGenericInstanceMethod(factory.String);

// importedMethod now references "!0[] System.Array.Empty<System.String>()"

See also Metadata Signatures for more information on type and method signatures.

Importing existing metadata definitions

References to existing metadata definitions can also be automatically converted to their appropriate reference type using an importer.

Below an example of how to import a type definition called SomeType:

ModuleDefinition externalModule = ModuleDefinition.FromFile(...);
TypeDefinition typeToImport = externalModule.TopLevelTypes.First(t => t.Name == "SomeType");

ITypeDefOrRef importedType = module.ImportType(typeToImport);

Most metadata descriptors implement the IImportable interface, and expose a member.ImportWith method allowing for fluent syntax:

ModuleDefinition externalModule = ModuleDefinition.FromFile(...);
ITypeDefOrRef importedType = externalModule
    .TopLevelTypes.First(t => t.Name == "SomeType")
    .ImportWith(module.DefaultImporter);

Importing existing type signatures

Type signatures can be imported in a similar fashion:

TypeSignature signature = ...;
TypeSignature imported = importer.ImportTypeSignature(signature);
TypeSignature signature = ...;
TypeSignature imported = signature.ImportWith(importer);
Note

If a corlib type signature is imported, the appropriate type from the CorLibTypeFactory of the target module will be selected, regardless of whether CorLib versions are compatible with each other.

Importing using System.Reflection

Types and members can also be imported by passing on an instance of various System.Reflection classes.

Member type to import Method to use Result type
Type ImportType ITypeDefOrRef
Type ImportTypeSignature TypeSignature
MethodBase ImportMethod IMethodDefOrRef
MethodInfo ImportMethod IMethodDefOrRef
ConstructorInfo ImportMethod IMethodDefOrRef
FieldInfo ImportScope MemberReference

There is limited support for importing complex types and members. Types that can be imported through reflection include:

  • Pointer types.
  • By-reference types.
  • Array types (If an array contains only one dimension, a SzArrayTypeSignature is returned. Otherwise a ArrayTypeSignature is created).
  • Generic parameters.
  • Generic type instantiations.
  • Generic method instantiations.
  • Function pointer types (.NET 8.0+ only).
Warning

Due to quirks in the System.Reflection sub-system of .NET, System.Type instances that represent function pointer types often lose their calling conventions, unless explicitly attained with methods such as GetModifiedFieldType, GetModifiedPropertyType, or GetModifiedParameterType. This includes the use of typeof (e.g., typeof(delegate*unmanaged[Cdecl]<void>) returns the same as typeof(delegate*managed<void>).

References imported using ImportField and ImportMethod may therefore also strip the calling conventions from function pointers that are the types of fields or in method signatures. If you need to handle this, manually set the types in the resulting IMethodDefOrRef or MemberReference to the appropriate type from ImportType.

Common Caveats using the Importer

Caching and reuse of instances

The default implementation of ReferenceImporter does not maintain a cache. Each call to any of the import methods will result in a new instance of the imported member. The exception to this rule is when the member passed onto the importer is defined in the module the importer is targeting itself, or was already a reference imported by an importer into the target module. In both of these cases, the same instance of this member definition or reference will be returned instead.

Importing cross-framework versions

The ReferenceImporter does not support importing across different versions of the target framework. Members are being imported as-is, and are not automatically adjusted to conform with other versions of a library.

As a result, trying to import from for example a library part of the .NET Framework into a module targeting .NET Core or vice versa has a high chance of producing an invalid .NET binary that cannot be executed by the runtime. For example, attempting to import a reference to [System.Runtime] System.DateTime into a module targeting .NET Framework will result in a new reference targeting a .NET Core library (System.Runtime) as opposed to the appropriate .NET Framework library (mscorlib).

This is a common mistake when trying to import using metadata provided by System.Reflection. For example, if the host application that uses AsmResolver targets .NET Core but the input file is targeting .NET Framework, then you will run in the exact issue described in the above.

var reference = importer.ImportType(typeof(DateTime));

// `reference` will target `[mscorlib] System.DateTime` when running on .NET Framework, and `[System.Private.CoreLib] System.DateTime` when running on .NET Core.

Therefore, always make sure you are importing from a .NET module that is compatible with the target .NET module.