TypeDef vs. TypeRef

Many of the .NET docs use the phrase "TypeDef or TypeRef". What's the difference?

Both refer to metadata tokens.  Each token has a scope for resolving the token, which basically corresponds to a module (a .dll or .exe). IMetaDataImport is the interface for reading metadata from a module.

A TypeDef refers to a type definition within a scope. A TypeRef refs to a TypeDef in another scope.

So a TypeDef is the "real type definition". Whereas a TypeRef just refers to a type you imported from another module.

A TypeDef contains interesting things like the name, base class (specified as either a TypeDef or TypeRef token), flags, implemented interfaces, methods, fields, and other members.

A TypeRef just contains the name and a token (an AssemblyRef or ModuleRef) referring to the other scope.  TypeRefs get resolved to TypeDefs by:
1) Finding the IMetaDataImport associated with the AssemblyRef. This is hard because the CLR loads modules dynamically at runtime. (A debugger can do this via ICorDebugModule2::ResolveAssembly)
2) Looking up a TypeDef by name in that new scope.  

An example:

Here's an example. We have a class hierarchy: Derived2 --> Derived1 --> Base --> System.Object. Derived1 + Derived2 are defined in a dll; Base in another, and System.Object in another (mscorlib.dll).

Base.cs:

 public class Base
{
    public int m_baseField;
}

 

Derived.cs:

 public class Derived2 : Derived1
{
    public int m_Two;
}

public class Derived1 : Base
{
    public int m_One;
}

Compile it like so:

csc /t:library base.cs /debug+
csc /t:library derived.cs /debug+ /r:base.dll

 

Simple case: Same module

Now, If you enable "show token values" in ILDasm and then crack open derived.dll and view the definition of the Derived2 class, you see:

 .class /*02000003*/ public auto ansi beforefieldinit Derived2
extends Derived1/*02000002*/
{
} // end of class Derived2

Recall that the top 2 digits of a token are the token type which specify which metadata table. Table 02 is TypeDefs, 01 is TypeRefs, and 23 is AssemblyRefs.
The bottom 6 digits are the row of the table.

So Derived2 is token 02000003, which is TypeDef (02) row 000003, derives from token 02000002, which is the TypeDef for Derived1. Since both classes are defined in the same module, everything here is TypeDefs.

 

Harder case: different modules

If you view base.dll and view the definition of the Base class, you see:

 .class /*02000002*/ public auto ansi beforefieldinit Base
       extends [mscorlib/*23000001*/]System.Object/*01000001*/
{
} // end of class Base

 

We see 'Base' gets token 02000002, which is table 02 (typedefs), and row 000002. And it derives from token 01000001, which is TypeRef #1.  Ildasm also shows us that TypeRef#1 has the following properties:

- a name "System.Object"
- a Resolution Scope 23000001, which is row #1 in the AssemblyRef table.  ILDasm conveniently looks this up within the module scope and provides the information in the class definition. 

You can see the assembly ref in the module's manifest: 

 .assembly extern /*23000001*/ mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 2:0:0:0
}

When an app wants to load an assembly, the loader / fusion / etc can use this information to load the assembly.

 

Compile binding vs. Runtime binding

Note that because of runtime binding, The base class for Derived1 is not resolved until Base.dll is actually loaded. So you could compile against one copy of Base.dll, but then swap out a different copy of Base.dll when it actually executes. This usually happens by accident, and could generate a TypeLoadException.

 

Other comments:

This Def vs. Ref thing is a common paradigm throughout the metadata. For example, there are AssemblyRefs, MethodDef + MethodRef, FieldDef and FieldRef, etc.

 

What about reflection?

There are multiple ways of describing types in .NET. Metadata is very explicit about scopes with a process. In contrast, Reflection tries to hide scopes and hide the distinction between TypeDef and TypeRef. Since reflection can resolve across scopes (or throw a TypeResolve Exception), it can always resolve TypeRefs and thus always present the TypeDef.

 

See https://dotnet.di.unipi.it/EcmaSpec/PartitionII/index.html for more information about metadata.