Safeguarding Dynamic Type Resolution
When Obfuscating .NET Assemblies with Skater
A field-oriented walkthrough showing how to keep Type.GetType, GetMethod, and Activator.CreateInstance functional after Skater renames symbols.
Core Principle
Reflection resolves types and members by string name at runtime. If Skater renames those names, the lookup fails. The solution is selective exclusion: tell Skater to leave only the reflection-critical identifiers alone, while still encrypting strings and scrambling control flow everywhere else. The rest of this guide shows exactly how to do that inside the Skater UI or via attributes.
1 Typical Breakage Scenarios
Most production .NET codebases contain at least one of the following patterns. Each of them becomes a runtime landmine the moment an obfuscator rewrites the underlying metadata names.
Plugin / Extension Loaders
Assemblies discovered at runtime and instantiated via Assembly.Load + Type.GetType.
Factory / DI Containers
Configuration-driven type creation that stores fully-qualified names in JSON, XML, or databases.
Serialization Contracts
System.Text.Json, Newtonsoft.Json, or DataContractSerializer mapping properties by name.
WPF / XAML Bindings
Code-behind types and event handlers referenced by string inside BAML resources.
2 Why Reflection Is Fragile Under Renaming
Static calls are rewritten by the obfuscator in both the caller and the callee, so they continue to work. Reflection, however, carries the original name as a literal string (or as a value loaded from configuration). When the metadata table no longer contains that string, the runtime returns null or throws.
The Mismatch in One Sentence
Your source still asks for "MyApp.PaymentGateway", but after a full rename the assembly only contains "a.b". The lookup fails.
3 Representative Code That Will Break
The following factory is intentionally simple yet realistic. It is the kind of code that works perfectly in Debug and Release builds and then crashes the moment aggressive renaming is applied.
using System;
using System.Reflection;
namespace MyApp.Plugins
{
public interface IGateway
{
void Send(string payload);
}
public class PaymentGateway : IGateway
{
public void Send(string payload)
{
Console.WriteLine($"Gateway received: {payload}");
}
}
public static class PluginFactory
{
public static IGateway CreateFromConfig(string typeName)
{
// string is often loaded from config or a database
Type t = Type.GetType(typeName);
if (t == null)
throw new TypeLoadException($"Cannot resolve {typeName}");
return (IGateway)Activator.CreateInstance(t);
}
}
}
After a naïve rename, Type.GetType("MyApp.Plugins.PaymentGateway") returns null and the factory throws.
4 Skater Exclusion Workflow
Skater gives you two complementary ways to keep the critical names intact. Use whichever fits your process.
Attribute-Driven (Preferred for Teams)
Decorate the types and members that must remain discoverable:
[System.Reflection.Obfuscation(Exclude = true, ApplyToMembers = true)]
public class PaymentGateway : IGateway { ... }
Skater honors the attribute automatically; no further UI action is required for those symbols.
UI Tree Exclusion
1. Open the assembly in Skater.
2. Expand the namespace tree until you reach the reflection targets.
3. Clear the “Rename” checkbox on the type and on each required member.
4. Leave string encryption and control-flow options enabled for everything else.
Project File Persistence
Save the exclusion rules into a Skater project file so the same settings can be replayed from the command line or a CI pipeline.
5 Decompiler Side-by-Side
After selective exclusion the public surface that reflection needs remains readable, while the method bodies are still heavily transformed.
6 Validation Checklist
- ✓ Run the full test suite against the obfuscated output, not the original Release build.
- ✓ Force every reflection path (plugin load, serializer round-trip, dynamic factory) to execute.
-
✓
Watch for
TypeLoadException,MissingMethodException, and null returns fromGetType/GetMethod. - ✓ If you ship plugins, verify that externally supplied type-name strings still resolve after the host is obfuscated.
7 Failure Matrix
| Observed Exception | Likely Cause | Skater Remedy |
|---|---|---|
| TypeLoadException | Type name renamed | Exclude type via attribute or UI checkbox |
| MissingMethodException | Method name renamed | Exclude the specific method node |
| JsonException / SerializationException | Property names no longer match JSON keys | Exclude DTO properties or add explicit name attributes |
| XamlParseException | BAML still points at old type names | Enable Skater’s WPF/XAML protection option |
8 Hardening Recommendations
Even when you still need a string, starting from nameof(PaymentGateway) makes the exclusion surface easier to maintain.
Put reflection-facing types in a small, well-documented surface area and apply maximum protection to everything behind it.
Store the Skater project file in source control and invoke the CLI from your build pipeline so exclusions never drift.