Home / Documentation / How to Obfuscate .NET Without Breaking Reflection
Technical Practical Guide

How to Obfuscate a .NET Application Without Breaking Reflection

Step-by-step instructions for protecting C# and VB.NET code with dynamic Type.GetType() and GetMethod() calls using Opaquer .NET Obfuscator.

Rustemsoft Technical Team • SEO Targets: .NET obfuscation reflection, obfuscate C# reflection • Updated for Opaquer 2026

Direct Answer

Obfuscating reflection-dependent .NET applications requires selective exclusion of runtime-resolved type and member names from identifier renaming while retaining protection for the underlying logic. In Opaquer .NET Obfuscator, you achieve this by selecting reflection-targeted types and methods in the assembly tree and disabling symbol renaming for those specific identifiers. This ensures metadata reflection APIs like Type.GetType() and GetMethod() succeed at runtime while string encryption and control-flow obfuscation continue to secure sensitive business logic.

1 The Problem

When a .NET obfuscator scrambles metadata identifiers, it replaces human-readable class, method, property, and field names (such as MyApp.Processor or ProcessData) with non-printable unicode characters, random string patterns, or encrypted metadata tokens.

Standard C# compilation bakes metadata tokens directly into method calls (e.g., IL call instance void MyApp.Processor::ProcessData()). Renaming these tokens in Intermediate Language (IL) changes both the declaration and the static call site in tandem, allowing direct method calls to execute without issue.

Dynamic Reflection Mechanics

Reflection operates dynamically via string literals or runtime metadata queries:

  • Type.GetType("MyApp.SomeClass") passes a hardcoded string to the runtime type resolver.
  • typeof(MyClass).GetMethod("ProcessData") queries assembly metadata for a method named "ProcessData".
  • Activator.CreateInstance("MyApp", "MyApp.SomeClass") dynamically locates and instantiates a type by string.

If an obfuscator renames MyApp.SomeClass to a.a or \u0001, the runtime reflection lookup searches for the literal string "MyApp.SomeClass", fails to find it in the obfuscated metadata table, and throws a runtime exception.

Runtime Reflection Lookup Comparison

Unprotected Assembly SUCCESS
Target Type: "MyApp.SomeClass"
Metadata Table: "MyApp.SomeClass"
Reflection Result: Valid Type Instance
Naively Obfuscated CRASH
Target Query: "MyApp.SomeClass"
Metadata Table: "a.a"
Reflection Result: null / TypeLoadException

2 Reflection-Dependent Code Example

Below is a practical implementation featuring dynamic type loading and method invocation commonly found in factory patterns, dependency injection engines, and plugin architectures.

ReflectionFactory.cs
using System;
using System.Reflection;

namespace MyApp
{
    public interface IProcessor
    {
        void ProcessData(string payload);
    }

    public class DataProcessor : IProcessor
    {
        public void ProcessData(string payload)
        {
            Console.WriteLine($"[Processing Payload]: {payload}");
        }
    }

    public class ReflectionFactory
    {
        public static void ExecuteDynamicCall()
        {
            // 1. Dynamic type resolution via literal string
            Type targetType = Type.GetType("MyApp.DataProcessor");
            if (targetType == null)
            {
                throw new InvalidOperationException("Failed to locate target type via reflection.");
            }

            // 2. Dynamic instantiation
            object instance = Activator.CreateInstance(targetType);

            // 3. Dynamic method resolution
            MethodInfo method = targetType.GetMethod("ProcessData");
            if (method == null)
            {
                throw new MissingMethodException("MyApp.DataProcessor", "ProcessData");
            }

            // 4. Dynamic invocation
            method.Invoke(instance, new object[] { "Encrypted_Order_Payload_9921" });
        }
    }
}

3 What Happens Under Naive Obfuscation

When an application containing dynamic reflection calls is processed using default, aggressive symbol renaming:

1. Type Renaming

The class MyApp.DataProcessor is scrambled to A.a.

2. Method Renaming

The method ProcessData is scrambled to b().

3. Execution Crash

Type.GetType("MyApp.DataProcessor") returns null, breaking execution.

Because the literal string "MyApp.DataProcessor" inside your code remains unchanged while assembly metadata is mutated to randomized symbols, standard runtime lookup fails completely.

4 Step-by-Step Opaquer Configuration

Follow these steps inside Opaquer .NET Obfuscator to keep reflection operational while protecting the underlying application logic.

1

Identify Reflection-Dependent Members

Audit your codebase for dynamic reflection invocations like Type.GetType(), Assembly.GetType(), Activator.CreateInstance(), or string queries passed to GetMethod().

2

Open the Assembly in Opaquer

Launch Opaquer .NET Obfuscator. Click Open File and load your primary target assembly (.exe or .dll). Opaquer inspects the assembly metadata and constructs a hierarchical visual tree under the Settings tab.

3

Locate Affected Classes and Methods

Expand the namespace nodes in the left panel to locate reflection targets (e.g., expand MyApp → DataProcessor → ProcessData).

4

Exclude Target Members from Name Obfuscation

In the tree view, uncheck Rename Type for MyApp.DataProcessor and uncheck symbol renaming for the ProcessData method node.

Alternative (Attribute-Based Exclusion):

Decorate C# code directly before compiling using standard attributes:

[System.Reflection.Obfuscation(Exclude = true, ApplyToMembers = true)]
5

Keep String Encryption & Control-Flow Enabled

Excluding a type name from renaming does not leave it unprotected. Keep Strings Encryption enabled to encrypt string literals inside method bodies, and keep High-Intensity Control Flow active to obfuscate execution logic.

5 Before/After Visual Decompiler View (ILSpy)

Compare how decompilers like ILSpy view your code under different obfuscation configurations:

Naive Obfuscation (Breaks Reflection) Broken
// Type & Method names are scrambled
namespace a
{
public class a : b
{
public void a(string A_0)
{
// Type.GetType("MyApp.DataProcessor") returns NULL!
Console.WriteLine(A_0);
}
}
}
Opaquer Selective Protection Preserved + Shielded
// Signature intact, body heavily obfuscated
namespace MyApp
{
public class DataProcessor : IProcessor
{
public void ProcessData(string payload)
{
int num = 14389201;
while (true) {
// Opaque predicates & string decryption
switch (num ^ 0xDBB820) { ... }
}
}
}
}

6 Testing the Obfuscated Application

Always validate protected binaries in your build pipeline prior to distribution:

1. CI/CD Integration Tests

Run unit and integration tests directly against obfuscated build output rather than raw Debug or Release assemblies.

2. Reflection Diagnostics

Intercept standard reflection exceptions: TypeLoadException, MissingMethodException, and NullReferenceException.

3. Dynamic Plugin Check

Ensure dynamically discovered assemblies loaded via Assembly.LoadFrom() match expected string contracts.

7 Common Failures and Fixes

Symptom / Exception Root Cause Solution in Opaquer
TypeLoadException Class name scrambled by symbol renaming. Uncheck Rename Type in tree view or use [Obfuscation(Exclude = true)].
MissingMethodException Method name scrambled during member obfuscation. Disable member renaming for target method node under its class tree.
JsonSerializationException Property names renamed; serializer failed mapping. Exclude DTO properties or use explicit attributes like [JsonPropertyName].
XamlParseException (WPF) XAML view paths miss renamed code-behind types. Enable Obfuscate XAML in WPF application option in Opaquer.

8 Production Recommendations

✓
Prefer nameof() Over String Literals:

Refactor legacy string queries to nameof(MyApp.DataProcessor) for compile-time safety and easier exclusion mapping.

✓
Isolate Reflection Targets into Integration Contracts:

Isolate external contract interfaces in dedicated namespaces (e.g., MyApp.Contracts) while applying maximum protection to core internal engines.

✓
Automate via Opaquer CLI:

Save exclusion rules into an Opaquer project file (.opq) and trigger batch protection in Azure DevOps or GitHub Actions builds.

9 Frequently Asked Questions

Related Opaquer Guides