Type Signatures

Note

The documentation has a new home: Check it out!

Type 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;
using AsmResolver.DotNet.Signatures.Types;

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)

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 = new TypeReference(corlibScope, "System", "Int32");
int32Type = module.CorLibTypeFactory.FromType(int32TypeRef);

If an invalid element type, name or type descriptor is passed on, these methods return null.

Class and Struct Types

The TypeDefOrRefSignature class is used to reference types in either the TypeDef or TypeRef (and sometimes TypeSpec) metadata table.

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

Alternatively, CreateTypeReference can be used on any IResolutionScope:

var streamTypeSig = corlibScope.CreateTypeReference("System.IO", "Stream");

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. Always use the CorLibTypeSignature to reference basic types within your blob signatures.

Generic Instance Types

The GenericInstanceTypeSignature class is used to instantiate generic types with type arguments:

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

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

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

Alternatively, a generic instance can also be generated via the MakeGenericType fluent syntax method:

var listOfString = corlibScope
    .CreateTypeReference("System.Collections.Generic", "List`1")
    .MakeGenericInstanceType(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 using a MethodSignature:

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)`.

Alternatively, a function pointer signature can also be generated via the MakeFunctionPointerType fluent syntax method:

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)`.

Shortcuts

To quickly transform any ITypeDescriptor into a TypeSignature, it is possible to use the .ToTypeSignature() method on any ITypeDescriptor. For TypeReference s, this will also check whether the object is referencing a basic type and return the appropriate CorLibTypeSignature instead.

var streamTypeRef = new TypeReference(corlibScope, "System.IO", "Stream");
var streamTypeSig = streamTypeRef.ToTypeSignature();

Likewise, a TypeSignature can also be converted back to a ITypeDefOrRef, which can be referenced using a metadata token, using the TypeSignature.ToTypeDefOrRef() method.

Decorating Types

Type signatures can be annotated with extra properties, such as an array or pointer specifier.

Below an example of how to create a type signature referencing System.Int32[]:

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

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

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

Adding decorations to types can also be done through shortcut methods that follow the MakeXXX naming scheme:

var arrayTypeSig = module.CorLibTypeFactory.Int32.MakeSzArrayType();

Below an overview of all factory shortcut 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.

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;

However, if you wish to configure the comparer (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);

Note

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

In some cases, however, exact type equivalence is too strict of a test. 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:

if (type1.IsCompatibleWith(type2))
{
    // type1 can be converted to type2.
}
if (type1.IsAssignableTo(type2))
{
    // Values of type1 can be assigned to variables of type2.
}