SmartAssembly 8

Obfuscating serializable types generated by the compiler

Using certain types of lambda expressions may prevent parts of your application from being obfuscated.

Some lambda expressions cause the compiler to generate additional helper classes behind-the-scenes (also called display classes). Those classes are marked with [Serializable, CompilerGenerated] attributes.

SmartAssembly automatically excludes all types marked as [Serializable] and their parents from obfuscation to ensure your application works as expected after build.


In most cases, it is safe to obfuscate compiler-generated serializable types. However, there are a few rare scenarios in which your application will actually serialize and deserialize such types, and obfuscating them could cause problems.

Starting with SmartAssembly 7.4.2 we introduced an option to force the obfuscation of compiler-generated serializable types to further protect your assembly.

How compiler-generated serializable classes are generated and how to obfuscate them?

The following example shows a simple command-line application. It uses a lambda expression to join arguments passed to the application into a single string.

namespace ConsoleApp1
{
    class Example
    {
        static void Main(string[] args)
        {
            System.Func<string[], string> join = array => string.Join(" ", array);
            var text = join(args);
            System.Console.WriteLine(text);
        }
    }
}

The join variable holds a lambda expression, processing an array parameter. Then, the lambda is executed on args argument and stored into text variable, written to the console in the next line.

Before obfuscation

The application's structure, as decompiled by .NET Reflector, can be seen below.

Notice a display class generated by the compiler after using a lambda expression.

namespace ConsoleApp1
{
    internal class Example
    {
        // Methods
        public Example();
        private static void Main(string[] args);

        // Nested Types
        [Serializable, CompilerGenerated]
        private sealed class <>c
        {
            // Fields
            public static readonly Example.<>c <>9;
            public static Func<string[], string> <>9__0_0;

            // Methods
            static <>c();
            public <>c();
            internal string <Main>b__0_0(string[] array);
        }
    }
}

After obfuscation (with compiler-generated serializable obfuscation disabled)

As seen below, ConsoleApp1.Example.Main method was obfuscated, along with its parameter, class and namespace names.

However, the ConsoleApp1.Example.<>c display class was not obfuscated due to [Serializable] attribute applied.

Unobfuscated namespace Console1 and class name Example can reveal how your application is structured. This can be improved (see next section).

namespace 
{
    internal class 
    {
        // Methods
        /* private scope */ static void (string[] );
    }
}
namespace ConsoleApp1
{
    internal class Example
    {
        // Methods
        public Example();

        // Nested Types
        [Serializable, CompilerGenerated]
        internal sealed class <>c
        {
            // Fields
            public static readonly Example.<>c <>9;
            public static Func<string[], string> <>9__0_0;

            // Methods
            internal string (string[] );
            static <>c();
            public <>c();
        }
    }
}

After obfuscation (with compiler-generated serializable obfuscation enabled)

As seen below, ConsoleApp1.Example.Main method was obfuscated, along with its parameter, class and namespace names.

Additionally, the ConsoleApp1.Example.<>c display class was obfuscated as well, and moved to a different class and namespace with obfuscated names, despite [Serializable] attribute applied.

All namespaces, classes and methods were mangled, making it significantly more difficult to understand how your application works.

namespace 
{
    internal class 
    {
        // Methods
        /* private scope */ static void (string[] );
    }
}
namespace 
{
    internal class 
    {
        // Methods
        public ();

        // Nested Types
        [Serializable, CompilerGenerated]
        internal sealed class 
        {
            // Fields
            public static readonly .. ;
            public static Func<string[], string> ;

            // Methods
            internal string (string[] );
            static ();
            public ();
        }
    }
}


How to enable compiler-generated serializable obfuscation?

To further protect your assembly, select option Enable obfuscation of serializable types generated by the compiler.

Option affects private types marked as [Serializable, CompilerGenerated] and their parent types.

In most scenarios, enabling this option will not cause any issues and will improve the protection of your assembly.

When is it unsafe to obfuscate compiler-generated serializable?

Enabling obfuscation of serializable types generated by the compiler may break your application when your application relies on:

  • serialization or deserialization of lambda expressions,
  • .NET Remoting with serializable types,
  • serialization or deserialization of any other types with both [Serializable] and [CompilerGenerated] attributes applied,


Serialization of any type relies on the type's full name (including parent types and namespace), and names of its properties and fields. The process of deserialization requires the names to be the same.

If your application uses serialization to serialize compiler-generated objects (such as lambdas), different builds of your application may become incompatible after enabling the new option.

Obfuscation of serializable types is unsafe due to:

  1. Obfuscated names compatibility between each build.
    Each time an assembly is obfuscated by SmartAssembly, type and field names can be different, especially when the assembly has changed. As a result, you might not be able to deserialize obfuscated type which was serialized in previous builds of your application.
  2. Serializing unobfuscated type and trying to deserialize it as obfuscated type.
    Example: Your unobfuscated application saves a private [Serializable, CompilerGenerated] type (e.g. lambda expression) to a file. Then, you try to load and deserialize the type using the obfuscated version of your assembly.
    Data serialized in unobfuscated application cannot be loaded by obfuscated application and vice versa, as the type and field names differ.
  3. Using different name mangling levels between builds.
    If assembly A uses ASCII name mangling and assembly B uses Unicode name mangling, the type and field names will differ.


If your application contains compiler-generated serializable types (that includes lambda expressions), but only part of them are serialized or deserialized, you can still utilize the new option. For example, your application might utilize serialization to store or transfer some lambda expressions, but others should be obfuscated.

  1. Select option Enable obfuscation of serializable types generated by the compiler
  2. Exclude lambda expressions your application serializes or deserializes from obfuscation. You can do that by adding [DoNotObfuscateType] attribute (see Using custom attributes) to the type containing the serialized lambda expression, or through Exclusions dialog in the GUI.












Didn't find what you were looking for?