Why Reflection Breaks After Obfuscation (and How to Prevent It)
Compilation is one of the most popular methods that is one of the most common ways. In order for programs like ILSpy or dotPeek to generate unintelligible output rather than your actual source structure, it renames types, methods, fields, and properties to meaningless identifiers.
Most of the time this works transparently, the compiler bakes method and type references directly into IL, so when an obfuscator renames a symbol, it changes every call site at the same time. The build still runs still runs as identically as before.
Reflection is the exception. Because reflection resolves types and members by string at runtime, it sits outside the obfuscator’s normal rewriting process. This article explains why that mismatch exists, how to recognize it, and how to design around it, independent of which obfuscation tool you’re using.
The Core Mechanism
Consider an ordinary method call:
var processor = new DataProcessor();
processor.ProcessData(payload);
This compiles to an IL callvirt instruction that references the method by metadata token, not by name. When an obfuscator renames DataProcessor to A and ProcessData to b, it rewrites the metadata token’s associated name and every call site that references that token. Declaration and call site change together, atomically, as part of the same rewrite pass.
Now consider the reflection equivalent:
var type = Type.GetType("MyApp.DataProcessor");
var method = type.GetMethod("ProcessData");
method.Invoke(instance, args);
Here, “MyApp.DataProcessor” and “ProcessData” are ordinary string literals. The obfuscator has no built-in way to know that these particular strings are meant to resolve against type metadata at runtime, syntactically, they’re indistinguishable from any other string in your program, like a log message or a UI label. The obfuscator renames the type and method as usual, but has no reason to touch the string literals, because touching arbitrary strings on the assumption they might be type names would be unsafe and unpredictable.
The result: at runtime, Type.GetType searches the assembly’s metadata table for “MyApp.DataProcessor” and finds nothing, because the actual type is now named something like a.a. You get null, which typically cascades into a NullReferenceException, TypeLoadException, or MissingMethodException depending on where the lookup happens.
Where This Shows Up in Practice
This isn’t a theoretical edge case, it surfaces in several extremely common .NET patterns:
Plugin and factory architectures. Any system that loads types dynamically based on configuration, naming convention, or assembly scanning (Type.GetType, Assembly.CreateInstance, Assembly.GetTypes().Where(…)) is affected.
JSON and XML serialization. System.Text.Json, Newtonsoft.Json, and XML serializers map document keys to property names via reflection. If a DTO’s property is renamed, deserialization doesn’t throw, it just silently leaves the property at its default value, which is often harder to diagnose than a crash.
Dependency injection containers. Some DI frameworks resolve services by convention-based name matching rather than purely by registered type, particularly in older or more dynamic configurations.
WPF and XAML. Compiled XAML (BAML) references code-behind classes and bindings by name. If a class bound in XAML is renamed without updating the corresponding BAML reference, the application throws a XamlParseException at window initialization.
Attribute-based routing and reflection-driven frameworks. ASP.NET MVC/Web API controllers, test framework discovery (xUnit, NUnit), and ORMs that map database columns to property names via reflection are all susceptible to the same failure pattern.
The General Fix: Selective Exclusion
The fix is the same in principle across virtually every .NET obfuscation tool, though the mechanics differ:
- Identify every reflection entry point, anywhere a type or member name is referenced as a string rather than compiled directly. Serialization DTOs, plugin interfaces, DI-resolved types, and XAML-bound classes are the usual suspects.
- Exclude just those identifiers from renaming, not the surrounding logic. Excluding a type from name obfuscation does not exempt it from other protections, string encryption, control-flow obfuscation, and anti-tamper measures on the method bodies typically still apply. You’re giving up exactly one layer (identifier secrecy) for that specific member, not the whole protection stack.
- Prefer declarative exclusion over manual configuration where possible. Most mainstream .NET obfuscators recognize the standard System.Reflection.ObfuscationAttribute:
[System.Reflection.Obfuscation(Exclude = true)]
public class PluginEntryPoint
{
[System.Reflection.Obfuscation(Exclude = true)]
public void Execute() { ... }
}
This attribute is part of the base class library, not tied to any single vendor, so it travels with your source and is visible in code review, which matters, because “this name is load-bearing for reflection” is exactly the kind of constraint that’s easy to forget six months later when someone refactors the class.
A Practical Checklist
Before obfuscating a build, it’s worth auditing for:
- Any Type.GetType, Activator.CreateInstance, or Assembly.CreateInstance call using a string literal or configuration value
- DTOs or POCOs that pass through JSON/XML serialization
- Classes or members referenced from XAML/BAML
- Reflection-based test discovery or attribute-driven frameworks
- Convention-based DI registrations (resolving by type name rather than interface)
For each, decide explicitly whether the identifier needs to stay intact for reflection to succeed, and exclude only that identifier, at the narrowest scope that resolves the problem.
Conclusion
Reflection and obfuscation aren’t fundamentally incompatible, they just operate on different layers of the same program. Compiled calls are resolved by the runtime using metadata tokens that obfuscators rewrite consistently; reflection calls are resolved using strings that obfuscators can’t safely infer meaning from. Understanding that distinction is enough to avoid the vast majority of obfuscation-related runtime failures: find the string-based lookups, exclude exactly those names, and leave everything else protected.
ASP.NET Core 11.0 Hosting Recommendation
HostForLIFE.eu
HostForLIFE.eu is a popular recommendation that offers various hosting choices. Starting from shared hosting to dedicated servers, you will find options fit for beginners and popular websites. It offers various hosting choices if you want to scale up. Also, you get flexible billing plans where you can choose to purchase a subscription even for one or six months.
