参照されているすべてのアセンブリを強制的にアプリドメインにロードする方法はありますか?


83

私のプロジェクトは次のように設定されています。

  • プロジェクト「定義」
  • プロジェクト「実施」
  • プロジェクト「消費者」

プロジェクト「Consumer」は「Definition」と「Implementation」の両方を参照しますが、「Implementation」のタイプを静的に参照しません。

アプリケーションが起動すると、プロジェクト「Consumer」は「Definition」の静的メソッドを呼び出します。このメソッドは「Implementation」でタイプを見つける必要があります。

パスや名前を知らなくても、できれば本格的なIOCフレームワークを使用せずに、参照されているアセンブリを強制的にApp Domainにロードする方法はありますか?


1
それはどのような問題を引き起こしていますか?なぜ強制的にロードする必要があるのですか?
Mike Two

おそらく静的な依存関係がないため、まったくロードされていません
Daniel Schaffer 2010年

実装でどのように「タイプを見つける」ことを試みていますか?特定のインターフェースを実装するものをお探しですか?
Mike Two

2
@マイク:はい。私はAppDomain.CurrentDomain.GetAssembliesを実行しており、linqクエリを使用してそれぞれでGetTypes()を再帰的に呼び出しています。
ダニエルシャファー2010年

回答:


90

これはトリックを行うようでした:

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
var loadedPaths = loadedAssemblies.Select(a => a.Location).ToArray();

var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();

toLoad.ForEach(path => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));

Jonが指摘したように、理想的なソリューションは、ロードされた各アセンブリの依存関係に戻る必要がありますが、私の特定のシナリオでは、それについて心配する必要はありません。


更新: .NET4に含まれているManagedExtensibility Framework(System.ComponentModel)には、このようなことを実現するためのはるかに優れた機能があります。


5
これは私には機能しません。ロードされていない参照アセンブリは、AppDomain.CurrentDomain.GetAssemblies()に表示されません。うーん...
テッド

11
どんな施設?検索しても何も見つかりませんでした。
nuzzolilo 2014

8
MEFを使用すると、上記のコードを次のように短縮できますnew DirectoryCatalog(".");(参照が必要System.ComponentModel.Composition)。
Allon Guralnek 2015

1
この回答は私の問題を解決し、私のために働きました。別のアセンブリを参照するMS単体テストプロジェクトがあり、テストの実行時にAppDomain.CurrentDomain.GetAssemblies()がそのアセンブリを返しませんでした。ユニットテストでそのライブラリのコードを使用していても、通常の実行と比較してvs.netがMSユニットテストプロジェクト(クラスライブラリ)をロードする方法が原因で、アセンブリが「GetAssemblies」に表示されなかった可能性があります。 .exeアプリケーション。コードがリフレクションを使用していて、単体テストに失敗している場合は、注意が必要です。
ディーン・ルンツ2016年

4
追加したいだけです。動的にロードされるアセンブリに注意してください。呼び出されたメンバーは動的アセンブリではサポートされていません。IsDynamic = falseのアセンブリを除外するか、負荷にフォールトトレラントである可能性がある場合は、CurrentDomain.Loadの呼び出しを試行/キャッチしてください。そしてassembly.Location。それもチェックする必要があります。IsDynamicアセンブリでは機能しません。
Eli Gassert 2017年

63

を使用してAssembly.GetReferencedAssembliesを取得しAssemblyName[]Assembly.Load(AssemblyName)それぞれを呼び出すことができます。もちろん、再帰する必要がありますが、できれば、すでにロードしたアセンブリを追跡することをお勧めします:)


私はそれを見つけましたが、問題は、参照されたアセンブリから何をしているのかをしなければならないことです...そして少なくともユニットテストのコンテキストでは、GetCallingAssembly、GetExecutingAssemblyはもちろん参照されたアセンブリを返し、GetEntryAssemblyはnullを返します:\
Daniel Schaffer 2010年

4
参照アセンブリをロードした後であれば、上記で問題が解決します。特定のタイプのtypeof(T).Assemblyに問い合わせることもできます。必要なのは、実装を含む(参照されていない)アセンブリを動的にロードすることだと思います。この場合、名前の静的リストを保持して手動でロードするか、ディレクトリ全体を調べてロードし、適切なインターフェイスでタイプを見つける必要があります。
Fadrian Sudaman 2010年

1
@vanhelgen:私の経験では、明示的に必要になることはめったにありません。通常、CLRの「ロードオンデマンド」は正常に機能します。
Jon Skeet 2015年

2
通常の状況ではそうかもしれませんが、DIコンテナを使用して(を介してSystem.Reflection)利用可能なサービスを検出すると、当然、まだロードされていないアセンブリに含まれているサービスは検出されません。それ以来の私のデフォルトのアプローチは、アプリのCompositionRoot内のすべての参照されるアセンブリのランダムなタイプからダミーのサブクラスを作成して、すべての依存関係が適切に配置されていることを確認することでした。起動時間がさらに長くなるという犠牲を払っても、すべてを事前にロードすることで、このナンセンスをスキップできることを願っています。@JonSkeetこれを行う別の方法はありますか?thx
mfeineis 2015年

12
これが当てはまるのは、GetReferencedAssembliesが明らかに「最適化された」リストを返すことです。したがって、参照されるアセンブリでコードを明示的に呼び出さない場合、そのリストは含まれません。(この議論による
FTWinston 2018

23

再帰的な例を共有したかっただけです。私は次のようにスタートアップルーチンでLoadReferencedAssemblyメソッドを呼び出しています:

foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    this.LoadReferencedAssembly(assembly);
}

これは再帰的な方法です。

private void LoadReferencedAssembly(Assembly assembly)
{
    foreach (AssemblyName name in assembly.GetReferencedAssemblies())
    {
        if (!AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == name.FullName))
        {
            this.LoadReferencedAssembly(Assembly.Load(name));
        }
    }
}

5
循環アセンブリ参照によってスタックオーバーフロー例外がスローされる可能性があるのではないかと思います。
Ronnie Overby 2016

1
Ronnie、私はそうは思わない、コードnameはまだない場合にのみ再帰を実行します。AppDomain.CurrentDomain.GetAssemblies()つまり、foreach選択されたものAssemblyNameがまだロードされていない場合にのみ再帰します。
Felype 2017年

1
私は約満足していないO(n^2)。このアルゴリズム(のランタイムGetAssemblies().Any(...)内部A foreach))。を使用しHashSetて、これをO(n)。のオーダーの何かに下げます。
Dai

16

Fody.Costuraまたはその他のアセンブリマージソリューションを使用している場合、受け入れられた回答は機能しません。

以下は、現在ロードされているアセンブリの参照アセンブリをロードします。再帰はあなたに任されています。

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();

loadedAssemblies
    .SelectMany(x => x.GetReferencedAssemblies())
    .Distinct()
    .Where(y => loadedAssemblies.Any((a) => a.FullName == y.FullName) == false)
    .ToList()
    .ForEach(x => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(x)));

このスニペットをどこに置くべきかアドバイスしてください。
テレマット2014年

1
あなたのブートローダー/スタートアップで私は想像します。
Meirion Hughes 2014

1
私は間違っているかもしれませんが、あなたはあなたをチェックすることができると思い!y.IsDynamicます.Where
Felype 2017

1

今日、特定のパスからアセンブリと依存関係をロードする必要があったので、それを行うためにこのクラスを作成しました。

public static class AssemblyLoader
{
    private static readonly ConcurrentDictionary<string, bool> AssemblyDirectories = new ConcurrentDictionary<string, bool>();

    static AssemblyLoader()
    {
        AssemblyDirectories[GetExecutingAssemblyDirectory()] = true;
        AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;

    }

    public static Assembly LoadWithDependencies(string assemblyPath)
    {
        AssemblyDirectories[Path.GetDirectoryName(assemblyPath)] = true;
        return Assembly.LoadFile(assemblyPath);
    }

    private static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
    {
        string dependentAssemblyName = args.Name.Split(',')[0] + ".dll";
        List<string> directoriesToScan = AssemblyDirectories.Keys.ToList();

        foreach (string directoryToScan in directoriesToScan)
        {
            string dependentAssemblyPath = Path.Combine(directoryToScan, dependentAssemblyName);
            if (File.Exists(dependentAssemblyPath))
                return LoadWithDependencies(dependentAssemblyPath);
        }
        return null;
    }

    private static string GetExecutingAssemblyDirectory()
    {
        string codeBase = Assembly.GetExecutingAssembly().CodeBase;
        var uri = new UriBuilder(codeBase);
        string path = Uri.UnescapeDataString(uri.Path);
        return Path.GetDirectoryName(path);
    }
}

1
辞書が必要ないことを除いて、良いコードです。この場合、単純なリストで十分です。元のコードでは、ロードされたアセンブリとロードされていないアセンブリを知る必要があると思います。そのため、辞書があります。
Reinis

0

さらに別のバージョン(Daniel Schafferの回答に基づく)は、すべてのアセンブリをロードする必要はないかもしれないが、事前定義された数のアセンブリをロードする必要がある場合です。

var assembliesToLoad = { "MY_SLN.PROJECT_1", "MY_SLN.PROJECT_2" };

// First trying to get all in above list, however this might not 
// load all of them, because CLR will exclude the ones 
// which are not used in the code
List<Assembly> dataAssembliesNames =
   AppDomain.CurrentDomain.GetAssemblies()
            .Where(assembly => AssembliesToLoad.Any(a => assembly.GetName().Name == a))
            .ToList();

var loadedPaths = dataAssembliesNames.Select(a => a.Location).ToArray();

var compareConfig = StringComparison.InvariantCultureIgnoreCase;
var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll")
    .Where(f =>
    {
       // filtering the ones which are in above list
       var lastIndexOf = f.LastIndexOf("\\", compareConfig);
       var dllIndex = f.LastIndexOf(".dll", compareConfig);

       if (-1 == lastIndexOf || -1 == dllIndex)
       {
          return false;
       }

       return AssembliesToLoad.Any(aName => aName == 
          f.Substring(lastIndexOf + 1, dllIndex - lastIndexOf - 1));
     });

var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();

toLoad.ForEach(path => dataAssembliesNames.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));

if (dataAssembliesNames.Count() != AssembliesToLoad.Length)
{
   throw new Exception("Not all assemblies were loaded into the  project!");
}

0

コンパイル時にコードが参照されないアセンブリがある場合、プロジェクトまたはnugetパッケージを参照として追加した場合でも、それらのアセンブリは他のアセンブリへの参照として含まれません。これはDebugReleaseビルド設定、コード最適化などに関係ありません。これらの場合、Assembly.LoadFrom(dllFileName)アセンブリをロードするには、明示的に呼び出す必要があります。


0

参照されるアセンブリを名前で取得するには、次の方法を使用できます。

public static Assembly GetAssemblyByName(string name)
{
    var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == name);
    if (asm == null)
        asm = AppDomain.CurrentDomain.Load(name);
    return asm;
}

0

私のwinformsアプリケーションでは、JavaScript(WebView2コントロール内)にさまざまな.NETのものを呼び出す可能性を与えています。たとえばMicrosoft.VisualBasic.Interaction、アセンブリMicrosoft.VisualBasic.dllのメソッド(InputBox()など)です。

しかし、私のアプリケーション自体はそのアセンブリを使用しないため、アセンブリが読み込まれることはありません。

したがって、アセンブリを強制的にロードするために、Form1_Loadにこれを追加するだけで済みました。

if (DateTime.Now < new DateTime(1000, 1, 1, 0, 0, 0)) { // never happens
  Microsoft.VisualBasic.Interaction.Beep();
  // you can add more things here
}

コンパイラはアセンブリが必要になる可能性があると考えていますが、実際にはこれはもちろん起こりません。

あまり洗練されたソリューションではありませんが、迅速で汚いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.