.Net Coreプラットフォームでコンソールアプリを作成していますが、C#の動的機能を使用してアセンブリ(.dllファイル)をロードし、クラスをインスタンス化する方法について疑問に思いました。 .Net 4.Xとは大きく異なるようで、実際には文書化されていません...
たとえば、クラスライブラリ(.Net Core)があり、クラスが1つしかないとします。
namespace MyClassLib.SampleClasses
{
public class Sample
{
public string SayHello(string name)
{
return $"Hello {name}";
}
public DateTime SayDateTime()
{
return DateTime.Now;
}
}
}
したがって、dllファイルの名前はMyClassLib.dll
とその位置は/dlls/MyClassLib.dll
。
次に、これを単純なコンソールアプリ(.Net Core)に読み込み、Sample
クラスをインスタンス化し、次のコンソールアプリのC#の動的機能を使用してメソッドを呼び出します。
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
// load the Assembly and use the classes
}
}
}
注: .Net CoreによってRC2バージョンを意味します。
それが最善の方法であるかどうかはわかりませんが、私が思いついたのは次のとおりです:
(。Net Core RC2でのみテスト済み-Windows)
_using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
var asl = new AssemblyLoader();
var asm = asl.LoadFromAssemblyPath(@"C:\Location\Of\" + "SampleClassLib.dll");
var type = asm.GetType("MyClassLib.SampleClasses.Sample");
dynamic obj = Activator.CreateInstance(type);
Console.WriteLine(obj.SayHello("John Doe"));
}
public class AssemblyLoader : AssemblyLoadContext
{
// Not exactly sure about this
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
var Assembly = Assembly.Load(new AssemblyName(res.First().Name));
return Assembly;
}
}
}
}
_
_MyClassLib.SampleClasses
_は名前空間であり、Sample
はタイプ別名クラス名です。
実行されると、メモリに_SampleClassLib.dll
_コンパイル済みクラスライブラリをロードしようとし、コンソールアプリに_MyClassLib.SampleClasses.Sample
_へのアクセス権を与え(質問を見て)、メソッドがSayHello()
そして、「John Doe」を名前として渡すため、プログラムは次のように出力します。
_"Hello John Doe"
_
クイックノート:メソッドLoad
のオーバーライドは重要ではないので、単に置き換えるだけでもよいその内容はthrow new NotImplementedException()
であり、私たちが気にするものには何も影響しないはずです。
現在、netcoreapp1.0
独自のAssemblyLoader
を実装する程度まで実際に行く必要はありません。正常に機能するDefault
が存在します。 (したがって、@ [VSV24]はLoad
が何もしないことに言及しています)。
using System;
using System.Runtime.Loader;
namespace AssemblyLoadingDynamic
{
public class Program
{
public static void Main(string[] args)
{
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\MyDirectory\bin\Custom.Thing.dll");
var myType = myAssembly.GetType("Custom.Thing.SampleClass");
var myInstance = Activator.CreateInstance(myType);
}
}
}
project.json
以下のように見えます:
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.1"
},
"System.Runtime.Loader": "4.0.0"
},
"frameworks": {
"netcoreapp1.0": {
"imports": "dnxcore50"
}
}
}
共有していただきありがとうございます。 Net Core 1.0でも動作しています。アセンブリで同じパスに別のアセンブリが必要な場合は、以下のコードサンプルを使用できます。
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyModel;
public class AssemblyLoader : AssemblyLoadContext
{
private string folderPath;
public AssemblyLoader(string folderPath)
{
this.folderPath = folderPath;
}
protected override Assembly Load(AssemblyName assemblyName)
{
var deps = DependencyContext.Default;
var res = deps.CompileLibraries.Where(d => d.Name.Contains(assemblyName.Name)).ToList();
if (res.Count > 0)
{
return Assembly.Load(new AssemblyName(res.First().Name));
}
else
{
var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
if (File.Exists(apiApplicationFileInfo.FullName))
{
var asl = new AssemblyLoader(apiApplicationFileInfo.DirectoryName);
return asl.LoadFromAssemblyPath(apiApplicationFileInfo.FullName);
}
}
return Assembly.Load(assemblyName);
}
}
project.json
ファイルに次の依存関係を追加することを忘れないでください:
"System.Runtime.Loader"
"Microsoft.Extensions.DependencyModel"
.net core 1.1/standard 1.6を使用して、AssemblyLoaderが利用できないことがわかりました。
AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath)
最後に、このソリューションは私にとってはうまくいきました-AssemblyNameオブジェクトを取得するためのステップを追加するだけです。それが動けなくなる人を助けることを願っています:
var assemblyName = AssemblyLoadContext.GetAssemblyName(assemblyPath); var Assembly = Assembly.Load(assemblyName);
@Rob、あなたのサンプルをビルドする唯一の方法は、「myInstance」タイプをdynamicに変更することでした。
タイプをvarのままにすると、コードをビルドできますが、ランタイムにロードされたアセンブリからメソッドを使用しようとするとすぐに、myInstanceなどのコンパイラエラーが発生しますメソッドXは含まれません。私はこれは初めてですが、タイプを動的としてマークすることは理にかなっているようです。型が実行時にロードされる場合、コンパイラはどのようにしてmyInstanceにメソッドXまたはprop Yが含まれるかを検証できますか? myInstanceを動的として入力することにより、コンパイラチェックを削除していると思われるため、サンプルをビルドして実行することができます。これが100%正しい方法であるかどうかはわかりません(十分に知りませんし、ダイナミックを使用すると問題があることをお勧めしますか?) AssemblyLoader(正しく指摘したとおり)。
そう...
using System;
using System.Runtime.Loader;
namespace TestApp
{
class Program
{
static void Main(string[] args)
{
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\Documents\Visual Studio 2017\Projects\Foo\Foo\bin\Debug\netcoreapp2.0\Foo.dll");
var myType = myAssembly.GetType("Foo.FooClass");
dynamic myInstance = Activator.CreateInstance(myType);
myInstance.UpperName("test");
}
}
}
これが誰かが新しいものとして役立つことを願っていますが、myInstance varがメソッドXなどDohを持っていなかった理由を特定するのに何年もかかりました!
私はそれに深く掘り下げて、DependencyContextアプローチを試しました...それはうまく機能しますが、いくつかの制限があり、アプリを起動するc ++ドットネットアプリにある標準のアセンブリ解像度とは異なります。手動で名前の一致を行う必要があり、ホストアプリが公開されている場合、子アセンブリがデバッグ中にnugetを使用している場合、問題(解決可能)であるnugetフォルダーのプローブパスがありません...
したがって、別の解決策があります:手動でアセンブリ(assemblyB)を読み込むアプリ(assemblyA)に依存関係がない(またはassemblyBと競合する依存関係がない)場合は、assemblyBのAssembly解像度をチートしてデフォルトにすることをお勧めします。 dotnet.exeには、選択したdepsファイルをロードできるようにする隠しgemがあり、次のようなことができます。
_dotnet exec --depsfile pathToAssemblyB\assemblyB.deps.json --runtimeconfig pathToAssemblyB\assemblyB.runtimeconfig.json AssemblyA.dll
_
他の回答で説明されているように、アセンブリをロードできます
var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath("pathToAssemblyB\\AssemblyB.dll");
これにより、assemblyBのすべての依存関係が正しく解決されますが、assemblyAの場合は解決されません。これは逆の問題ですが、大きなアプリでリモート処理を行いたい小さなアプリがある場合は便利です。もう1つの問題は、アプリの起動時にassemblyBを使用することと、実行ごとに1回だけ機能することを知る必要があることです。そのため、さまざまな問題があり、状況に応じてアプローチを選択できます。サポートされていない/文書化されていない機能ですが、EFコアツールで使用されているため、現時点では「実行可能」であることに注意してください。