.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.