C# > Advanced C# > Attributes and Reflection > Dynamic Type Creation
Dynamic Type Creation with Attributes and Reflection
This example demonstrates how to dynamically create a type at runtime, apply custom attributes to it, and then access these attributes using reflection. This technique is useful for scenarios where you need to generate types based on runtime configuration or data, and then use attributes to control their behavior.
Understanding Dynamic Type Creation
Dynamic type creation involves generating new classes, interfaces, or other type definitions during the execution of a program, rather than at compile time. This is achieved using classes within the System.Reflection.Emit
namespace. This powerful technique allows applications to adapt to changing requirements, integrate with external systems, or implement plugin architectures. Attributes provide metadata about a type or member. Using reflection, you can inspect this metadata.
Code: Defining a Custom Attribute
First, we define a custom attribute called ConfigurationAttribute
. This attribute can be applied to classes and structs. It has two properties: SettingName
and DefaultValue
. The constructor takes these two values as parameters. The AttributeUsage
attribute restricts where this attribute can be applied.
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class ConfigurationAttribute : Attribute
{
public string SettingName { get; set; }
public string DefaultValue { get; set; }
public ConfigurationAttribute(string settingName, string defaultValue)
{
SettingName = settingName;
DefaultValue = defaultValue;
}
}
Code: Dynamic Type Creation and Attribute Application
This code snippet demonstrates the dynamic creation of a type named MyConfigurableClass
. It uses AssemblyName
and AssemblyBuilder
to create a dynamic assembly and module. The TypeBuilder
is used to define the new type. The important part is applying the ConfigurationAttribute
using CustomAttributeBuilder
. Reflection is then used to read the attribute's properties after the type has been created. A default constructor is defined for the dynamic type, although it's optional for this example to function. The CreateType()
method finalizes the type creation. The Main
method demonstrates how to use the created type and retrieve the attribute's values using reflection.
using System;
using System.Reflection;
using System.Reflection.Emit;
public class DynamicTypeExample
{
public static Type CreateConfigurableType(string typeName)
{
AssemblyName assemblyName = new AssemblyName("DynamicAssembly");
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public);
// Apply the ConfigurationAttribute
ConstructorInfo attributeConstructor = typeof(ConfigurationAttribute).GetConstructor(new Type[] { typeof(string), typeof(string) });
CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(attributeConstructor, new object[] { "DatabaseConnection", "localhost" });
typeBuilder.SetCustomAttribute(attributeBuilder);
// Create a default constructor (optional)
ConstructorBuilder constructorBuilder = typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
Type dynamicType = typeBuilder.CreateType();
return dynamicType;
}
public static void Main(string[] args)
{
Type dynamicType = CreateConfigurableType("MyConfigurableClass");
// Use Reflection to read the attribute
ConfigurationAttribute attribute = (ConfigurationAttribute)dynamicType.GetCustomAttribute(typeof(ConfigurationAttribute));
if (attribute != null)
{
Console.WriteLine($"Setting Name: {attribute.SettingName}");
Console.WriteLine($"Default Value: {attribute.DefaultValue}");
}
}
}
Concepts Behind the Snippet
Key concepts involved are Reflection.Emit
for dynamic code generation and Reflection
for inspecting the generated code. The AssemblyBuilder
and ModuleBuilder
are crucial for defining the assembly and module that will contain the dynamic type. The TypeBuilder
is the central class for defining the type itself, including its methods, properties, and attributes. The CustomAttributeBuilder
allows you to apply attributes to the dynamically created type, mimicking how you would apply them in regular C# code. Finally, reflection is used to inspect the created type and access its attributes at runtime.
Real-Life Use Case Section
This technique is extremely useful in scenarios where you need to create types based on configuration data that is only available at runtime. For example, imagine a plugin system where each plugin defines its own data structures. Using dynamic type creation and attributes, you could load the plugin configuration, create the necessary data types, and then use attributes to specify how these types should be handled by the system. Another use case is data mapping. You might need to map data from an external source (like a database or API) to C# objects, where the structure of the external data is not known at compile time. Dynamic type creation lets you create the classes on the fly to match the external schema.
Best Practices
TypeLoadException
, ArgumentException
, and MissingMethodException
gracefully.Type
objects to improve performance.
Interview Tip
When discussing dynamic type creation in an interview, emphasize your understanding of the underlying concepts: reflection, Reflection.Emit
, and the trade-offs involved. Be prepared to discuss real-world scenarios where this technique would be appropriate, and highlight your awareness of security considerations.
When to Use Them
Memory Footprint
Dynamic type creation does incur a memory footprint. Each dynamically created type consumes memory in the application domain. The memory overhead includes the type's metadata, method bodies, and any associated data. Excessive dynamic type creation without proper management can lead to memory leaks or increased memory consumption. Therefore, it's important to carefully consider the number of dynamic types created and to release resources appropriately when they are no longer needed.
Alternatives
Alternatives to dynamic type creation include:Dictionary
) or an ExpandoObject
to represent dynamic data. This avoids creating new types but may sacrifice type safety.
Pros
Cons
FAQ
-
What is the purpose of AssemblyBuilderAccess.Run?
AssemblyBuilderAccess.Run
specifies that the dynamic assembly should be created in memory and can be executed directly. It does not save the assembly to disk. -
How can I save a dynamic assembly to disk?
You can useAssemblyBuilderAccess.Save
when defining theAssemblyBuilder
. You'll also need to specify a file name for the assembly and callAssemblyBuilder.Save
to persist it to disk. -
Is dynamic type creation thread-safe?
The dynamic code generation itself is not inherently thread-safe. You should ensure proper synchronization if you're creating dynamic types from multiple threads concurrently.