When we use a lot of external dependencies, the generated folder may contain a lot of Dlls that are not conducive to distribution.

Before

Here are two ways to pack multiple assemblies into one.

Costura.Fody


Costura is an add-in for Fody. Fody is an extensible tool for weaving .net assemblies. It enables the manipulating the IL of an assembly as part of a build.

Costura is easy to use. Install Fody and Costura.Fody NuGet package to the target project, and add a file called FodyWeavers.xml with the following content to project root folder.

<Weavers>
  <Costura/>
</Weavers>

Regenerate the project, you will find that all the dependencies are gone and only the main program remains.

Costura

All config options are accessed by modifying the Costura node in FodyWeavers.xml. For detailed instructions, please see the instructions in the github repo.

Config Defaults
CreateTemporaryAssemblies false
IncludeDebugSymbols true
IncludeRuntimeReferences true
DisableCompression false
DisableCleanup false
LoadAtModuleInit true
IgnoreSatelliteAssemblies false
ExcludeAssemblies / ExcludeRuntimeAssemblies null
IncludeAssemblies / IncludeRuntimeAssemblies null
Unmanaged32Assemblies & Unmanaged64Assemblies null

ILMerge


ILMerge is a static linker for .NET Assemblies by Microsoft. It's a stand-alone application so we can use it in the command line. It is very easy to use but the more recommended usage is as a Nuget package.

Install ILMerge Nuget package to the target project. Edit the project .csproj file (inside the respective <Project> .. </Project> tags). Here is the basic usage example.

<Target Name="ILMerge" AfterTargets="Build">
    <!-- the ILMergePath property points to the location of ILMerge.exe console application -->
    <Exec Command="$(ILMergeConsolePath) /out:$(OutputPath)$(AssemblyName).dll $(OutputPath)ClassLibrary1.dll $(OutputPath)ClassLibrary2.dll" />
</Target>

<!-- delete the dependency files after merge -->
<Target Name="_ProjectRemoveDependencyFiles" AfterTargets="ILMerge">
    <ItemGroup>
      <_ProjectDependencyFile Include="$(OutputPath)ClassLibrary1.dll" />
      <_ProjectDependencyFile Include="$(OutputPath)ClassLibrary2.dll" />
    </ItemGroup>
    <Delete Files="@(_ProjectDependencyFile)" />
</Target>

Regenerate the project and get the merged assembly.

The configuration and default values are also given here. For a detailed description, please see the repo wiki.

command line options defaults
[/allowDup[:typeName]]* no duplicates of public types allowed
/wildcards false
/zeroPeKind false
/attr:filename null
/closed false
/copyattrs false
/ndebug true
/internalize[:excludeFile] null
/align:n 512
/internalize[:excludeFile] false
/keyfile:filename null
/log false
/log[:logfile] null
/out:filename null
/useFullPublicKeyForReferences true
/lib:directory
/targetplatform:version,platformdirectory
/target:(library|exe|winexe) ILMerge.Kind.SameAsPrimaryAssembly
/union false
/ver:version null
/xmldocs false

The full command line for ILMerge is:

ilmerge [/lib:directory]* [/log[:filename]] [/keyfile:filename [/delaysign]] [/internalize[:filename]]
[/t[arget]:(library|exe|winexe)] [/closed] [/ndebug] [/ver:version] [/copyattrs [/allowMultiple]]
[/xmldocs] [/attr:filename] ([/targetplatform:<version>[,<platformdir>]]|v1|v1.1|v2|v4)
[/useFullPublicKeyForReferences] [/zeroPeKind] [/wildcards] [/allowDup[:typename]]*
[/allowDuplicateResources] [/union] [/align:n]
/out:filename <primary assembly> [<other assemblies>...]

Difference Between Two Ways


Decompile the generated assembly in dnspy, and we found:

  • Costura.Fody
  1. Costura embedded resources as a method of merging assemblies.
  2. Use AssemblyLoader into the module initializer when assembly is loaded into memory.

 Costura.Fody Decompile

  • ILMerge

By recompiling and packaging IL code, all types of related dependencies are directly included in the target assembly.

ILMerge Decompile

If you care about performance and memory usage very much, ILMerge will be a better choice.