Metadata (CLR)

Overview

In the Common Language Runtime (CLR), metadata is data that describes the structure of an assembly. It contains information about types, members, references, and security attributes, enabling the CLR to enforce type safety, perform reflection, and support language interoperability.

Metadata is stored in a binary format defined by the ECMA-335 Common Language Infrastructure (CLI) specification. When you compile a .NET source file, the compiler emits a Portable Executable (PE) file that includes both IL code and the associated metadata.

Types of Metadata

  • Assembly Metadata – Name, version, culture, public key token.
  • Module Metadata – Namespaces, types, and the logical layout of the assembly.
  • Type Metadata – Definitions of classes, structs, enums, interfaces, delegates.
  • Member Metadata – Methods, fields, properties, events, constructors.
  • Custom Attribute Metadata – User-defined attributes applied to any metadata element.

Metadata Tables

The CLR stores metadata in a series of tables, each containing rows that represent a particular kind of entity. Below is a simplified view of the most common tables:

Table Name          Description
--------------      ------------------------------
Assembly            Assembly identity
Module              Module identity
TypeDef             Defined types (class, struct, etc.)
MethodDef           Defined methods
FieldDef            Defined fields
Property            Defined properties
Event               Defined events
MemberRef           References to members in other modules
CustomAttribute     Custom attribute data
MethodImpl          Method implementations (IL, native)
InterfaceImpl       Implemented interfaces
# ... many more tables per ECMA-335 specification

Reading Metadata with Reflection

Reflection provides a high‑level API for inspecting metadata at runtime. The following C# example demonstrates how to enumerate all types in an assembly and list their public methods:

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        Assembly asm = Assembly.Load("System.Console");
        foreach (Type t in asm.GetTypes())
        {
            Console.WriteLine($"Type: {t.FullName}");
            foreach (MethodInfo m in t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
            {
                Console.WriteLine($"  Method: {m.Name}");
            }
        }
    }
}

Custom Attributes

Custom attributes are a powerful way to embed additional metadata. They are stored in the CustomAttribute table and can be retrieved via reflection.

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class DocumentationAttribute : Attribute
{
    public string Url { get; }
    public DocumentationAttribute(string url) => Url = url;
}

[Documentation("https://learn.microsoft.com/dotnet/standard/metadata")]
public class MyClass { }

Emitting Metadata (IL)

The System.Reflection.Emit namespace enables you to generate assemblies dynamically, including custom metadata. Below is a minimal example that creates a new assembly with a single type and method:

using System;
using System.Reflection;
using System.Reflection.Emit;

class DynamicGen
{
    static void Main()
    {
        AssemblyName aName = new AssemblyName("DynamicDemo");
        AssemblyBuilder asm = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.RunAndSave);
        ModuleBuilder mod = asm.DefineDynamicModule("MainModule", "DynamicDemo.dll");
        TypeBuilder tb = mod.DefineType("HelloWorld", TypeAttributes.Public);
        MethodBuilder mb = tb.DefineMethod("SayHello", MethodAttributes.Public | MethodAttributes.Static, typeof(void), Type.EmptyTypes);
        ILGenerator il = mb.GetILGenerator();
        il.EmitWriteLine("Hello from dynamically generated assembly!");
        il.Emit(OpCodes.Ret);
        tb.CreateType();
        asm.Save("DynamicDemo.dll");
    }
}

Metadata & Resources

In addition to type information, assemblies can contain embedded resources (images, strings, etc.). The .manifest table references these resources, and they can be accessed at runtime via Assembly.GetManifestResourceStream.