Shrinking .NET assemblies by removing unnecessary attributes

.NET assemblies contain a rich set
of metadata for use by the run time, debugger, and compiler. This information
is stored in the assembly whether it will be used or not which for certain
types of shipping assemblies is unnecessary. For example when you create a new
automatic property:

public int
MyProperty { get; set;
}

 

The compiler generates the following
disassembled code:

.field private int32
'<MyProperty>k__BackingField'

.method public hidebysig
specialname instance int32

   
get_MyProperty() cil managed

  {

    .custom instance void
[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() =
( 01 00 00 00 )

    //
Code size 11 (0xb)

   
.maxstack 1

   
.locals init (int32 V_0)

    IL_0000:
ldarg.0

   
IL_0001: ldfld int32 ConsoleApplication1.Test::'<MyProperty>k__BackingField'

   
IL_0006: stloc.0

   
IL_0007: br.s IL_0009

   
IL_0009: ldloc.0

   
IL_000a: ret

  } // end of
method Program::get_MyProperty

  .method private
hidebysig specialname instance void

   
set_MyProperty(int32 'value') cil managed

  {

    .custom
instance void
[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() =
( 01 00 00 00 )

   
// Code size 8 (0x8)

   
.maxstack 8

   
IL_0000: ldarg.0

   
IL_0001: ldarg.1

    IL_0002:
stfld int32 ConsoleApplication1.Test'<MyProperty>k__BackingField'

   
IL_0007: ret

  } // end of
method Program::set_MyProperty

Note the extra CompilerGeneratedAttribute that the
compiler inserts to note that this code was generated by the compiler. This
attribute is used by the debugger to step over code but when you’re done
debugging and ready to ship it adds little value.

Thanks to the Common Compiler Infrastructure Metadata
Components
we can easily create a tool to remove attributes from an
assembly. Using MetadataMutor we override Visit(List<ICustomAttribute>)
and return a modified list of attributes by removing all attributes of a
certain type:

public class AttributeMutator : MetadataMutator

{

    private Dictionary<string, string> attributesToRemove;

 

    public AttributeMutator(IMetadataHost host, IEnumerable<string> attributesToRemove)

   
: base(host)

    {

   
    this.attributesToRemove = new Dictionary<string, string>();

   
    foreach (string attribute in attributesToRemove)

   
{

   
        this.attributesToRemove.Add(attribute.ToUpperInvariant(), attribute);

   
}

    }

 

    public override List<ICustomAttribute>
Visit(List<ICustomAttribute> customAttributes)

    {

   
    List<ICustomAttribute> result = new List<ICustomAttribute>(customAttributes);

   
    for (int i = result.Count - 1; i >= 0; i--)

   
{

   
        if (this.attributesToRemove.ContainsKey(result[i].Type.ToString().ToUpperInvariant()))

   
{

   
result.RemoveAt(i);

   
}

   
}

   
    return base.Visit(result);

    }

}

The rest of the code to read
and write assemblies can be copied from the CCI samples.

Download the
Visual Studio 2010 project

To use this program run:

               StripAttributes.exe C:\Input\MyAssembly.dll C:\Output
@attributes.txt

where attributes.txt is
a list of attributes, one per line, to be removed from the assembly. The
resulting file is written to C:\Output. Since the assembly is rewritten it will
need to be resigned with sn.exe before shipping.

The cost savings per
assembly will vary depending on your attribute usage and how the PE pages line
up. Sometimes no savings can be achieved even if attributes are removed because
the assembly is written in 512 byte chunks.

For desktop CLR assemblies disk size is relatively unimportant but when the end user has to download your code (Silverlight for example) every byte matters.