Compiled Clean, Crashed at Runtime.
Your release build had zero warnings. You protected it with an obfuscator. Now something explodes on a customer machine. This guide walks through why that happens - and how to keep full protection while making the crash disappear.
The Short Version
.NET resolves most calls through compiled metadata tokens, which an obfuscator rewrites consistently. But a surprising amount of the framework resolves names by string at runtime: XAML binding paths, My.Settings and Properties.Settings, JSON serializers, reflection, plugin hosts, and the strong-name signature on the file itself. Rename a symbol that one of those strings points at, and the static build still succeeds while the runtime lookup misses. The fix is not "turn obfuscation off" - it is selective exclusion of those few names in Skater Pro, while renaming, control flow, and string encryption continue everywhere else.
One Program, Two Name Resolvers
Before touching a single exclusion setting, it helps to hold one idea firmly: a compiled .NET program resolves names in two completely different ways, and an obfuscator only understands one of them.
Resolver #1 - metadata tokens. When your C# calls invoiceService.Process(), the compiler emits a token pointing into the assembly metadata tables. If the obfuscator renames Process to b, it rewrites the token too. Nothing breaks. This is the world the obfuscator lives in, and it is also why most of your application - the private business logic - can be renamed aggressively with zero risk.
Resolver #2 - string lookups. XAML evaluates {Binding TotalDue} by searching the ViewModel for a property literally named TotalDue. The settings engine searches configuration for a property name stored in the .config file. Assembly.GetType("Plugin.Engine") does exactly what the string says. Nobody tells the obfuscator these strings exist. After renaming, the strings point at names that no longer exist - and you get the classic signature of an obfuscation bug: perfect build, dead runtime.
Five Crash Case Files
Each case below follows the same pattern you will use in production: recognize the symptom, trace it to a string lookup, exclude the right symbol.
Symptom. The very first line that touches configuration throws SettingsPropertyNotFoundException, or a VB app fails on My.SettingsFoo access.
Why. The ApplicationSettings provider matches setting wrappers to entries in app.config / user.config by property name at runtime. Renaming ConnectionString to a means the engine searches the config for "a" and finds nothing.
' VB.NET - works only if the property name survives protection
Dim cs As String = My.Settings.DbConnectionString
✓ Remedy: exclude the generated Settings/MySettings class from renaming in Skater Pro. String encryption on the values stays on.
Symptom. No exception at all. Textboxes render empty, converters never fire, and the Output window fills with binding-path errors. This is the nastiest case because it fails silently.
Why. {Binding CustomerName} is a string evaluated against the ViewModel's property names via reflection. Rename CustomerName and the path simply doesn't resolve.
✓ Remedy: exempt bindable ViewModel properties (or whole ViewModels) from renaming. If you prefer, decorate DTOs with explicit attributes instead - but ViewModel properties must keep their names.
Symptom. Deserialization either throws JsonException or, worse, succeeds with every property set to null.
Why. A serializer's contract with the wire format is built from property names. The JSON payload says "ApiKey"; the renamed C# property is c. Nothing matches.
// C# - the DTO is a wire contract; protect its logic, not its names
public class ApiConfig
{
public string? ApiKey { get; set; } // keep name; encrypt usage elsewhere
}
✓ Remedy: exclude DTO model classes from renaming - or keep names stable via [JsonPropertyName] mappings and let the rest of the model be renamed.
Symptom. Assembly.GetType("Plugin.PaymentEngine") returns null (VB) or a TypeLoadException fires; plugin hosts report missing entry points; MissingMethodException appears on external callers.
Why. Cross-assembly contracts - plugin interfaces, exported API methods, anything looked up by literal string - depend on names the obfuscator cannot see.
✓ Remedy: preserve public interface types and plugin entry points via Skater .NET Obfuscator exclusion rules; rename the internal implementation freely.
Symptom. The loader refuses the assembly: the strong-name signature no longer matches the file's hash.
Why. This one is not about symbol names at all. Obfuscation rewrites IL and metadata, so any signature computed before protection is invalidated by definition. A signed-then-obfuscated assembly is out of date the moment obfuscation finishes.
✓ Remedy: re-sign after protection. Point Skater .NET Obfuscator's signing settings at your .snk/.pfx key, or run sn -R as the final CI step - never before obfuscation.
The Skater .NET Obfuscator Workflow: Protect Everything, Rename Selectively
The goal is a protection policy you define once and apply on every build - not a losing game of whack-a-mole with individual errors.
-
Step 1.Inventory your string-based contracts
Search the solution for
GetType(,GetMethod(, XAML{Bindingpaths, serializer attributes,Properties.Settings/My.Settingsaccess, and plugin type names. This short list is your exclusion inventory. -
Step 2.Encode the inventory as Skater .NET Obfuscator exclusion rules
Mark each inventoried class, property, or method as excluded from renaming in Skater .NET Obfuscator's rules. Rules live in the project configuration, so the same exclusions apply to every teammate's build and every CI run automatically.
-
Step 3.Leave the heavy protection running everywhere
Excluding a name from renaming is not weakening the code behind it. Keep control flow obfuscation and string encryption enabled across the entire assembly - including the excluded classes. A decompiler still sees scrambled logic and encrypted constants; only the contract names stay readable.
-
Step 4.Re-sign as the final build stage
Configure the strong-name key in Skater .NET Obfuscator so signing happens after protection, in the same pipeline stage - removing the classic "signed too early" failure from Case File 05.
Pre-Flight Checklist for Every Obfuscated Release
Exclusion rules saved with the project, not recreated by hand each build.
Rename map archived as a build artifact so production stack traces can be decoded.
WPF binding trace listeners enabled during the smoke test - silent binding failures are the easiest to ship.
Strong-name verification passes: sn -v YourApp.exe.
Integration tests executed against the obfuscated binaries, not the clean build output.
One round of manual QA on a machine that has never run the un-obfuscated build.