◆ ILMerge
  ◆ targetplatform:v4
  ◆ VisualStudio の機能じゃないのでインストール必要
◆ WPF アプリは ILMerge で出力された exe が起動できない
  ◆ csproj の設定で依存 dll をリソースに埋め込む
  ◆ 手動でアセンブリの解決時にリソースを読みこませる

nuget のライブラリや 自分の dll ライブラリなど使うと ビルドした時に フォルダの中が dll まみれになります
1 つの exe にまとまってたほうがフォルダがすっきりするし 扱いやすいです

また PC 詳しくない人にアプリケーションを使ってもらうときに exe だけ移動させてしまってエラーになったなんて問題もなくなります

ILMerge

dll をまとめるといえば ILMerge


サンプルに dll を作る lib プロジェクトとコンソールアプリの ConsoleApplication1 を作ります
using System;
using lib;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(new A().getPrivate("val"));
        }
    }

    public class A
    {
        private int val { get; } = 100;
    }
}

ConsoleApplication の中身はこれだけ
lib を参照に加えて lib のほうには getPrivate 拡張メソッドで Reflection を使って private の値を取り出すようなコードを書いておきます

bin/Release フォルダには ConsoleApplication1.exe と lib.dll ができます

C:\tmpcodes\ConsoleApplication1\bin\Release>ConsoleApplication1.exe
100
そのまま実行すれば普通に結果が表示されます

この exe を単体で デスクトップなどに持っていて実行すると lib.dll がみつからないエラーが起きます


ILMerge で結合すると
C:\tmpcodes\ConsoleApplication1\bin\Release>"C:\Program Files (x86)\Microsoft\ILMerge\ILMerge.exe" ^
    ConsoleApplication1.exe *.dll ^
    /out:output.exe /wildcards /targetplatform:v4

C:\tmpcodes\ConsoleApplication1\bin\Release>output.exe
100

この output.exe をデスクトップに単体で持っていっても動きます

WPF

WPF でもやってみます

さっきのコードが MainWindow に移動したくらいです
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        MessageBox.Show(new A().getPrivate("val").ToString());
    }
}

同じオプションだと PresentaionFramework がないとエラーが起きるので lib で dll の場所を指定しました
C:\tmpcodes\WpfApp1\bin\Release>"C:\Program Files (x86)\Microsoft\ILMerge\ILMerge.exe" ^
    WpfApp1.exe *.dll /out:output.exe /wildcards /targetplatform:v4 ^
    /lib:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2"

これでエラー無く exe ファイルが作成されるのですが……

起動できません

ダブルクリックしても何も起きません

イベントビューアをみると
アプリケーション:output.exe
フレームワークのバージョン:v4.0.30319
説明: ハンドルされない例外のため、プロセスが中止されました。
例外情報:System.IO.IOException
   場所 MS.Internal.AppModel.ResourcePart.GetStreamCore(System.IO.FileMode, System.IO.FileAccess)
   場所 System.IO.Packaging.PackagePart.GetStream(System.IO.FileMode, System.IO.FileAccess)
   場所 System.IO.Packaging.PackagePart.GetStream()
   場所 System.Windows.Application.LoadComponent(System.Uri, Boolean)
   場所 System.Windows.Application.DoStartup()
   場所 System.Windows.Application.<.ctor>b__1_0(System.Object)
   場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
   場所 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
   場所 System.Windows.Threading.DispatcherOperation.InvokeImpl()
   場所 System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)
   場所 MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(System.Object)
   場所 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   場所 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   場所 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   場所 MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext, System.Threading.ContextCallback, System.Object)
   場所 System.Windows.Threading.DispatcherOperation.Invoke()
   場所 System.Windows.Threading.Dispatcher.ProcessQueue()
   場所 System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
   場所 MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
   場所 MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)
   場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
   場所 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
   場所 System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)
   場所 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
   場所 MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
   場所 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
   場所 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
   場所 System.Windows.Application.RunDispatcher(System.Object)
   場所 System.Windows.Application.RunInternal(System.Windows.Window)
   場所 System.Windows.Application.Run(System.Windows.Window)
   場所 WpfApp1.App.Main()

IOException になってます

ILMerge の問題かとググってみると XAML がロードできないので WPF では使えないとか

対策

ILMerge が使えないとどうしようもないかと思ったのですが 方法があるようです

http://www.telerik.com/blogs/how-to-merge-assemblies-into-wpf-application

WPF アプリの csproj の最後 </Project> の直前に これを追加します
<Target Name="AfterResolveReferences">
    <ItemGroup>
        <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
            <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
        </EmbeddedResource>
    </ItemGroup>
</Target>

これでビルドされる exe に dll ファイルが埋め込みリソースとして含まれるようになります
ただ それらのアセンブリは手動でロードしないといけなくなります

起動時にリソースから .dll ファイルを読み込んでおいて AssemblyResolve イベント時に要求された dll が埋め込みリソースにあればそれを返すようにしておきます
public class Program
{
    [STAThreadAttribute]
    public static void Main()
    {
        var assemblies = new Dictionary<string, Assembly>();
        var executingAssembly = Assembly.GetExecutingAssembly();
        var resources = executingAssembly.GetManifestResourceNames().Where(n => n.EndsWith(".dll"));

        foreach (var resource in resources)
        {
            using (var stream = executingAssembly.GetManifestResourceStream(resource))
            {
                if (stream == null)
                    continue;

                var bytes = new byte[stream.Length];
                stream.Read(bytes, 0, bytes.Length);
                try
                {
                    assemblies.Add(resource, Assembly.Load(bytes));
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.Print($"Failed to load: {resource}, Exception: {ex.Message}");
                }
            }
        }

        AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
        {
            var assemblyName = new AssemblyName(e.Name);

            var path = $"{assemblyName.Name}.dll";

            if (assemblies.ContainsKey(path))
            {
                return assemblies[path];
            }

            return null;
        };

        App.Main();
    }
}

リンクのページのもの ほぼままです
この書き方だと別に Main 関数をもう一つ作って 前処理が終わったら 本来の App.Main を呼び出しています

埋め込みが不要になったらクラスごと消すだけで楽ですが Main が複数になるので作ったときに スタートアップオブジェクトを選択する必要があります

プロジェクトのプロパティのアプリケーションタブで選べます
エントリポイントなので この Program.Main の方をえらべばおっけいです


できた exe ファイルをデスクトップなどに持っていっても動きます

Scripting で試した

dll が多かったり複雑そうなのだと大丈夫なのかな と思って Scripting で機能を試してみました
簡単にいうと eval するみたいなもの

正式な機能じゃないので Microsoft が出してるけど 外部ライブラリとして nuget でダウンロードすることになります
これをいれるだけで 30 弱のファイルが Release フォルダに追加されました

動かしてみると 単体 exe だとアセンブリをメソッドを呼び出すタイミングでエラーになりました
出力されたフォルダにある状態なら動いたので何が必要なのか調べてみたところ

一緒に出力される config ファイルがあれば動きました

App.config にはライブラリインストール時に自動で色々設定が追加されてるので必要そうとも言えます


逆に dll についてきた xml ファイルはなくても問題ありませんでした
中身を見ると doc と書いててメッセージ系ですが 数 MB はありますしビルドで出力される以上実行時に必要になりそうだけど使ってないのかな?



一見めんどくさそうな WPF の exe にまとめる方法ですが ILMerge も外部ツールなのでビルド後に ILMerge を実行しないといけません
一応 ビルド後イベントでコマンド実行するように設定すれば外部ツールの ILMerge を呼び出せますが ILMerge は VisualStudio についてるものでもないので 別にインストール必要です
となれば ILMerge よりも WPF の方法のほうが楽なのかもしれません

本当に VisualStudio だけの機能 でできればいいのですけど



dll 以外のファイルを exe ファイルにリソースとして含める方法はこっち
ファイルを exe に含める