Metadata is binary information embedded within .NET assemblies that describes every aspect of your code’s structure. Often called “data about data” or the assembly’s self-describing component, metadata contains complete descriptions of types, members, references, and other structural elements.
The core problem it solves is eliminating the need for separate type libraries (like COM type libraries) by making assemblies fully self-contained and discoverable at runtime.
Assembly metadata describes the assembly itself - its identity, dependencies, security requirements, and exported types. This information is stored in the assembly manifest.
using System.Reflection;
// Get assembly metadata through reflection
Assembly assembly = Assembly.GetExecutingAssembly();
// Access assembly-level metadata
AssemblyName assemblyName = assembly.GetName();
Console.WriteLine($"Name: {assemblyName.Name}");
Console.WriteLine($"Version: {assemblyName.Version}");
Console.WriteLine($"Culture: {assemblyName.CultureInfo?.Name ?? "neutral"}");
// Get custom assembly attributes
var companyAttr = assembly.GetCustomAttribute<AssemblyCompanyAttribute>();
Console.WriteLine($"Company: {companyAttr?.Company}");
// Example of defining assembly metadata via attributes
[assembly: AssemblyCompany("Acme Corporation")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyDescription("Sample assembly metadata demonstration")]
Assembly attributes are typically defined in AssemblyInfo.cs or using project properties in modern .NET.
A module is a single file containing IL code and metadata. While most assemblies contain a single module, multi-file assemblies have multiple modules. Module metadata describes the file itself and its contents.
// Access module-level metadata
Module[] modules = assembly.GetModules();
foreach (Module module in modules)
{
Console.WriteLine($"Module: {module.Name}");
Console.WriteLine($"FQN: {module.FullyQualifiedName}");
// Module-level custom attributes
var attributes = module.GetCustomAttributes();
foreach (var attr in attributes)
{
Console.WriteLine($"Module attribute: {attr}");
}
}
// Example showing module scope (less common in practice)
[module: CLSCompliant(true)] // Module-level attribute
public class ModuleMetadataExample { }
Type metadata provides complete descriptions of classes, structs, interfaces, enums, and delegates - including their base types, implemented interfaces, and membership.
using System;
// Define a type with various members
[Serializable] // Type-level attribute
public class Customer
{
public string Name { get; set; }
private int _age;
public Customer(string name) => Name = name;
}
// Reflect on type metadata
Type customerType = typeof(Customer);
Console.WriteLine($"Type: {customerType.Name}");
Console.WriteLine($"Namespace: {customerType.Namespace}");
Console.WriteLine($"Is Class: {customerType.IsClass}");
Console.WriteLine($"Base Type: {customerType.BaseType}");
// Get type attributes
var serializableAttr = customerType.GetCustomAttributes(typeof(SerializableAttribute), false);
Console.WriteLine($"Is Serializable: {serializableAttr.Length > 0}");
// Get members metadata
MemberInfo[] members = customerType.GetMembers(BindingFlags.Public | BindingFlags.Instance);
foreach (MemberInfo member in members)
{
Console.WriteLine($"Member: {member.Name} ({member.MemberType})");
}
Type Discovery: Use reflection to discover types dynamically, enabling plugin architectures and dependency injection.
Member metadata describes methods, properties, fields, events, and constructors - including signatures, parameters, return types, and accessibility.
using System.Reflection;
public class Order
{
[Required] // Member-level attribute
public string OrderNumber { get; set; }
private DateTime _orderDate;
public void ProcessOrder(int priority)
{
// Method implementation
}
}
// Reflect on member metadata
Type orderType = typeof(Order);
// Property metadata
PropertyInfo propInfo = orderType.GetProperty("OrderNumber");
Console.WriteLine($"Property: {propInfo.Name}");
Console.WriteLine($"Type: {propInfo.PropertyType}");
Console.WriteLine($"Can Write: {propInfo.CanWrite}");
// Check for custom attributes on members
var requiredAttr = propInfo.GetCustomAttribute<RequiredAttribute>();
Console.WriteLine($"Has Required attribute: {requiredAttr != null}");
// Method metadata with parameters
MethodInfo methodInfo = orderType.GetMethod("ProcessOrder");
ParameterInfo[] parameters = methodInfo.GetParameters();
foreach (ParameterInfo param in parameters)
{
Console.WriteLine($"Parameter: {param.Name}, Type: {param.ParameterType}");
}
- Reflection Principle Enablement: Enables runtime type discovery and dynamic invocation
- Attribute-Based Programming (DRY): Allows declarative behavior through attributes
- Tooling and Debugging Support: Provides rich IntelliSense and debugging information
Advanced Nuances
When working with very large assemblies, you can load only metadata without loading the actual IL code:
// Metadata-only loading prevents JIT compilation overhead
Assembly assembly = Assembly.ReflectionOnlyLoadFrom("LargeAssembly.dll");
// Can inspect metadata but cannot execute methods
public class DynamicTypeBuilder
{
public static void CreateTypeWithMetadata()
{
AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(
assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType("DynamicType", TypeAttributes.Public);
// Add properties/methods with full metadata
typeBuilder.DefineProperty("DynamicProp",
PropertyAttributes.HasDefault, typeof(string), null);
Type dynamicType = typeBuilder.CreateType();
// Now has full metadata like any other type
}
}
// Custom metadata provider pattern
public interface ICustomMetadataProvider
{
IEnumerable<CustomAttributeData> GetCustomMetadata();
}
public class EnhancedType : ICustomMetadataProvider
{
public IEnumerable<CustomAttributeData> GetCustomMetadata()
{
// Return custom metadata beyond standard attributes
yield return new CustomAttributeData(
typeof(DisplayNameAttribute).GetConstructor(new[] { typeof(string) }),
new object[] { "Enhanced Type" });
}
}
Performance: Reflection and metadata inspection can be expensive. Consider caching results for frequently accessed metadata.
Roadmap Context
Within the “Attributes and Reflection” section, metadata serves as the fundamental substrate that makes everything else possible. It’s the prerequisite for:
- Custom Attribute Design: Understanding how attributes are stored as metadata
- Advanced Reflection Patterns: Performance-optimized reflection techniques
- IL Generation and Dynamic Types: Creating and manipulating metadata
- AOP (Aspect-Oriented Programming): Compile-time and runtime AOP implementations