Table of Contents

Metadata Signatures

Type and member signatures represent references to types within a blob signature. They are not directly associated with a metadata token, but can reference types defined in one of the metadata tables.

All relevant classes in this document can be found in the following namespaces:

using AsmResolver.DotNet.Signatures;

Overview

Basic leaf type signatures:

Type signature name Example
CorLibTypeSignature int32 (System.Int32)
TypeDefOrRefSignature System.IO.Stream, System.Drawing.Point
GenericInstanceTypeSignature System.Collections.Generic.IList`1<System.Int32>
FunctionPointerTypeSignature method void *(int32, int64)
GenericParameterSignature !0, !!0
SentinelTypeSignature (Used as a delimeter for vararg method signatures)

Decorator type signatures:

Type signature name Example
SzArrayTypeSignature System.Int32[]
ArrayTypeSignature System.Int32[0.., 0..]
ByReferenceTypeSignature System.Int32&
PointerTypeSignature System.Int32*
CustomModifierTypeSignature System.Int32 modreq (System.Runtime.CompilerServices.IsVolatile)
BoxedTypeSignature (Boxes a value type signature)
PinnedTypeSignature (Pins the value of a local variable in memory)

Member signatures:

Signature name Example
FieldSignature System.Int32 (attached to a FieldDefinition)
MethodSignature instance System.Void *(System.Int32, System.Int32)
PropertySignature System.Int32 *() (attached to a PropertyDefinition)

Basic Element Types

The CLR defines a set of primitive types (such as System.Int32, System.Object) as basic element types that can be referenced using a single byte, rather than the fully qualified name. These are represented using the CorLibTypeSignature class.

Every ModuleDefinition defines a property called CorLibTypeFactory, which exposes reusable instances of all corlib type signatures:

ModuleDefinition module = ...
TypeSignature int32Type = module.CorLibTypeFactory.Int32;

Corlib type signatures can also be looked up by their element type, by their full name, or by converting a type reference to a corlib type signature.

int32Type = module.CorLibTypeFactory.FromElementType(ElementType.I4);
int32Type = module.CorLibTypeFactory.FromName("System", "Int32");

var int32TypeRef = corlibScope.CreateTypeReference("System", "Int32");
int32Type = module.CorLibTypeFactory.FromType(int32TypeRef);

If an invalid element type, name or type descriptor is passed on, FromType returns null.

Class and Struct Types

Type signatures from non-primitive types (e.g., TypeDefinitions or TypeReferences) can be created by using the ToTypeSignature on an instance of a ITypeDefOrRef:

// Create from a definition.
TypeDefinition type = module.TopLevelTypes.First(t => t.IsTypeOf("Namespace", "TypeName"));
var typeSig = type.ToTypeSignature();
// Create from a new type reference.
var typeSig = corlibScope
    .CreateTypeReference("System.IO", "Stream")
    .ToTypeSignature(isValueType: false);
// Create from an existing type reference, dynamically resolving isValueType
RuntimeContext context = ...
TypeReference reference = ...
var typeSig = reference.ToTypeSignature(context);

These examples create an TypeDefOrRefSignature instance (corresponding to either a class or valuetype type signature). You can also create this signature manually, by using its constructor:

TypeReference streamTypeRef = new TypeReference(corlibScope, "System.IO", "Stream");
TypeSignature streamTypeSig = new TypeDefOrRefSignature(streamTypeRef, isValueType: false);
Warning

While it is technically possible to reference a basic type such as System.Int32 as a TypeDefOrRefSignature, it renders the .NET module invalid by most implementations of the CLR. Prefer to use ToTypeSignature, or always use the CorLibTypeSignature to reference basic types within your blob signatures.

Generic Instance Types

To create generic instances of types, use MakeGenericInstanceType:

var listOfString = corlibScope
    .CreateTypeReference("System.Collections.Generic", "List`1")
    .MakeGenericInstanceType(isValueType: false, typeArguments: [module.CorLibTypeFactory.String]);

// listOfString now contains a reference to List<string>.

This can also be created by manually instantiating a GenericInstanceTypeSignature class:

var listTypeRef = new TypeReference(corlibScope, "System.Collections.Generic", "List`1");

var listOfString = new GenericInstanceTypeSignature(listTypeRef, 
    isValueType: false, 
    typeArguments: [module.CorLibTypeFactory.String]
);

// listOfString now contains a reference to List<string>.

Function Pointer Types

Function pointer signatures are strongly-typed pointer types used to describe addresses to functions or methods. In AsmResolver, they are represented by wrapping a MethodSignature into a FunctionPointerTypeSignature using MakeFunctionPointerType:

var factory = module.CorLibTypeFactory;
var type = MethodSignature.CreateStatic(
        factory.Void,
        factory.Int32, factory.Int32)
    .MakeFunctionPointerType();

// type now contains a reference to `method void *(int32, int32)`.

Alternatively, a FunctionPointerTypeSignature can be created manually as well:

var factory = module.CorLibTypeFactory;
var signature = MethodSignature.CreateStatic(
    factory.Void,
    factory.Int32, factory.Int32
);

var type = new FunctionPointerTypeSignature(signature);

// type now contains a reference to `method void *(int32, int32)`.

Decorating Types

Type signatures can be annotated with extra properties, such as an array or pointer specifier. This is done using the MakeXXXType methods.

var arrayTypeSig = module.CorLibTypeFactory.Int32.MakeSzArrayType();
// `arrayTypeSig` now references `System.Int32[]`

Below an overview of all factory methods:

Factory method Description
MakeArrayType(int dimensionCount) Wraps the type in a new ArrayTypeSignature with dimensionCount zero based dimensions with no upperbound.
MakeArrayType(ArrayDimension[] dimensions) Wraps the type in a new ArrayTypeSignature with dimensions set as dimensions
MakeByReferenceType() Wraps the type in a new ByReferenceTypeSignature
MakeModifierType(ITypeDefOrRef modifierType, bool isRequired) Wraps the type in a new CustomModifierTypeSignature with the specified modifier type.
MakePinnedType() Wraps the type in a new PinnedTypeSignature
MakePointerType() Wraps the type in a new PointerTypeSignature
MakeSzArrayType() Wraps the type in a new SzArrayTypeSignature
MakeGenericInstanceType(TypeSignature[] typeArguments) Wraps the type in a new GenericInstanceTypeSignature with the provided type arguments.

Decorations can also be created manually, by manually calling the constructor for each type signature:

var arrayTypeSig = new SzArrayTypeSignature(module.CorLibTypeFactory.Int32);

Traversing Signatures

Traversing type signature annotations can be done by accessing the BaseType property of TypeSignature.

var arrayElementType = arrayTypeSig.BaseType; // returns System.Int32

This gets the immediate base type as a TypeSignature.

Alternatively, all TypeSignatures available in AsmResolver also implement the visitor pattern, allowing for strongly-typed traversals of a type signature. This is typically very useful when traversing or extracting specific details from deep/complex type signatures:

public class MyVisitor : ITypeSignatureVisitor<TResult>
{
    public TResult VisitArrayType(ArrayTypeSignature signature)
    {
        /* ... handle boxed types ... */
        return signature.AcceptVisitor(this);
    }

    public TResult VisitBoxedType(BoxedTypeSignature signature)
    {        
        /* ... handle boxed types ... */
        return signature.AcceptVisitor(this);
    }

    /* ... */
}

TypeSignature signature = ...;
var result = signature.AcceptVisitor(new MyVisitor());

Comparing Types

Type signatures can be tested for semantic equivalence using the SignatureComparer class. Most use-cases of this class will not require any customization. In these cases, the default SignatureComparer can be used:

var comparer = SignatureComparer.Default;

To mimic the comparison the runtime does (including following forwarded types and strong name handling), use the signature comparer as stored in the RuntimeContext you are operating in:

var comparer = context.SignatureComparer;

if you wish to configure the comparer yourself (e.g., for relaxing some of the declaring assembly version comparison rules), it is possible to create a new instance instead:

var comparer = new SignatureComparer(SignatureComparisonFlags.AllowNewerVersions);

Once a comparer is obtained, we can test for type equality using any of the overloaded Equals methods:

TypeSignature type1 = ...;
TypeSignature type2 = ...;

if (comparer.Equals(type1, type2)) 
{
    // type1 and type2 are semantically equivalent.
}

The SignatureComparer class implements various instances of the IEqualityComparer<T> interface, and as such, it can be used as a comparer for dictionaries and related types:

var dictionary = new Dictionary<TypeSignature, TValue>(comparer);
Tip

The SignatureComparer class also implements equality comparers for other kinds of metadata, such as field and method descriptors and their signatures.

In some cases, exact type equivalence is too strict. Since .NET facilitates an object oriented environment, many types will inherit or derive from each other, making it difficult to pinpoint exactly which types we would need to compare to test whether two types are compatible with each other.

Section I.8.7 of the ECMA-335 specification defines a set of rules that dictate whether values of a certain type are compatible with or assignable to variables of another type. These rules are implemented in AsmResolver using the IsCompatibleWith and IsAssignableTo methods:

RuntimeContext? context = ...;
if (type1.IsCompatibleWith(type2, context)) 
{
    // type1 can be converted to type2.
}
RuntimeContext? context = ...;
if (type1.IsAssignableTo(type2, context)) 
{
    // Values of type1 can be assigned to variables of type2.
}

Member Signatures

TypeSignatures form the building blocks for more complex signatures.

This includes field signatures, used in FieldDefinition and MemberReference:

// System.Int32 Foo;
var field = new FieldDefinition(
    name: "Foo",
    attributes: FieldAttributes.Public,
    signature: new FieldSignature(module.CorLibTypeFactory.Int32)
);
// reference to `System.String::Empty`
var reference = module.CorLibTypeFactory.CorLibScope
    .CreateTypeReference("System", "String")
    .CreateMemberReference(
        name: "Empty",
        signature: new FieldSignature(module.CorLibTypeFactory.String)
    );

Method signatures can be created using the factory methods MethodSiganture.CreateStatic or MethodSignature.CreateInstance, and are used in both MethodDefinition and MemberReference:

// static System.Void Foo(System.Int32)
var method = new MethodDefinition(
    name: "Foo", 
    attributes: MethodAttributes.Static,
    signature: MethodSignature.CreateStatic(module.CorLibTypeFactory.Void, module.CorLibTypeFactory.Int32)
);
// instance System.String System.Int32::ToString()
var method = module.CorLibTypeFactory.CorLibScope
    .CreateTypeReference("System", "Int32")
    .CreateMemberReference(
        name: "ToString",
        signature: MethodSignature.CreateInstance(module.CorLibTypeFactory.String)
    );
// static !0 System.Linq.Enumerable::Empty<?>()
var method = module.CorLibTypeFactory.CorLibScope
    .CreateTypeReference("System.Linq", "Enumerable")
    .CreateMemberReference(
        name: "Empty",
        signature: MethodSignature.CreateInstance(
            returnType: new GenericParameterSignature(GenericParameterType.Method, 0), 
            genericParameterCount: 1,
            parameterTypes: []
        )
    );
Warning

Ensure that you use the right method signature factory method for the right method. Creating an instance signature for a static method or vice versa will cause the CLR to reject the final binary.