Пример консольной программы.
class Program
{
static void Main(string[] args)
{
// ... code to build dll ... not written yet ...
Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll");
// don't know what or how to cast here
// looking for a better way to do next 3 lines
IRunnable r = assembly.CreateInstance("TestRunner");
if (r == null) throw new Exception("broke");
r.Run();
}
}
Я хочу динамически построить сборку (.dll), а затем загрузить сборку, создать экземпляр класса и вызвать метод Run () этого класса. Стоит ли мне попробовать привести к чему-нибудь класс TestRunner? Не уверен, как типы в одной сборке (динамический код) узнают о моих типах в моем (статическом приложении сборки / оболочки). Не лучше ли использовать несколько строк кода отражения для вызова Run () только для объекта? Как должен выглядеть этот код?
ОБНОВЛЕНИЕ: Уильям Эдмондсон - см. Комментарий
c#
.net
reflection
BuddyJoe
источник
источник
export
иimport
классы в отдельных сборках, производных от известного интерфейсаОтветы:
Использовать домен приложения
Безопаснее и гибче
AppDomain
сначала загружать сборку в отдельную .Итак, вместо ответа, данного ранее :
var asm = Assembly.LoadFile(@"C:\myDll.dll"); var type = asm.GetType("TestRunner"); var runnable = Activator.CreateInstance(type) as IRunnable; if (runnable == null) throw new Exception("broke"); runnable.Run();
Я бы предложил следующее (адаптировано из этого ответа на связанный вопрос ):
var domain = AppDomain.CreateDomain("NewDomainName"); var t = typeof(TypeIWantToLoad); var runnable = domain.CreateInstanceFromAndUnwrap(@"C:\myDll.dll", t.Name) as IRunnable; if (runnable == null) throw new Exception("broke"); runnable.Run();
Теперь вы можете выгрузить сборку и иметь другие настройки безопасности.
Если вам нужна еще большая гибкость и мощность для динамической загрузки и выгрузки сборок, вам следует взглянуть на структуру управляемых надстроек (т.е.
System.AddIn
пространство имен). Дополнительные сведения см. В этой статье о надстройках и расширяемости в MSDN .источник
CreateInstanceFromAndUnwrap
требуется AssemblyName, а не путь; ты имел ввидуCreateFrom(path, fullname).Unwrap()
? Также меня обожглоMarshalByRefObject
требованиеCreateInstanceAndUnwrap(typeof(TypeIWantToLoad).Assembly.FullName, typeof(TypeIWantToLoad).FullName)
?Если у вас нет доступа к
TestRunner
информации о типе в вызывающей сборке (похоже, что у вас ее нет), вы можете вызвать метод следующим образом:Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll"); Type type = assembly.GetType("TestRunner"); var obj = Activator.CreateInstance(type); // Alternately you could get the MethodInfo for the TestRunner.Run method type.InvokeMember("Run", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, null);
Если у вас есть доступ к
IRunnable
типу интерфейса, вы можете привести к нему свой экземпляр (а неTestRunner
тип, который реализован в динамически созданной или загруженной сборке, верно?):Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll"); Type type = assembly.GetType("TestRunner"); IRunnable runnable = Activator.CreateInstance(type) as IRunnable; if (runnable == null) throw new Exception("broke"); runnable.Run();
источник
Я делаю именно то, что вы ищете, в моем движке правил, который использует CS-Script для динамической компиляции, загрузки и запуска C #. Он должен легко переводиться в то, что вы ищете, и я приведу пример. Во-первых, код (урезанный):
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using CSScriptLibrary; namespace RulesEngine { /// <summary> /// Make sure <typeparamref name="T"/> is an interface, not just any type of class. /// /// Should be enforced by the compiler, but just in case it's not, here's your warning. /// </summary> /// <typeparam name="T"></typeparam> public class RulesEngine<T> where T : class { public RulesEngine(string rulesScriptFileName, string classToInstantiate) : this() { if (rulesScriptFileName == null) throw new ArgumentNullException("rulesScriptFileName"); if (classToInstantiate == null) throw new ArgumentNullException("classToInstantiate"); if (!File.Exists(rulesScriptFileName)) { throw new FileNotFoundException("Unable to find rules script", rulesScriptFileName); } RulesScriptFileName = rulesScriptFileName; ClassToInstantiate = classToInstantiate; LoadRules(); } public T @Interface; public string RulesScriptFileName { get; private set; } public string ClassToInstantiate { get; private set; } public DateTime RulesLastModified { get; private set; } private RulesEngine() { @Interface = null; } private void LoadRules() { if (!File.Exists(RulesScriptFileName)) { throw new FileNotFoundException("Unable to find rules script", RulesScriptFileName); } FileInfo file = new FileInfo(RulesScriptFileName); DateTime lastModified = file.LastWriteTime; if (lastModified == RulesLastModified) { // No need to load the same rules twice. return; } string rulesScript = File.ReadAllText(RulesScriptFileName); Assembly compiledAssembly = CSScript.LoadCode(rulesScript, null, true); @Interface = compiledAssembly.CreateInstance(ClassToInstantiate).AlignToInterface<T>(); RulesLastModified = lastModified; } } }
Это примет интерфейс типа T, скомпилирует файл .cs в сборку, создаст экземпляр класса данного типа и выровняет этот экземпляр класса с интерфейсом T. По сути, вам просто нужно убедиться, что созданный класс реализует этот интерфейс. Я использую свойства для настройки и доступа ко всему, например:
private RulesEngine<IRulesEngine> rulesEngine; public RulesEngine<IRulesEngine> RulesEngine { get { if (null == rulesEngine) { string rulesPath = Path.Combine(Application.StartupPath, "Rules.cs"); rulesEngine = new RulesEngine<IRulesEngine>(rulesPath, typeof(Rules).FullName); } return rulesEngine; } } public IRulesEngine RulesEngineInterface { get { return RulesEngine.Interface; } }
В вашем примере вы хотите вызвать Run (), поэтому я бы создал интерфейс, определяющий метод Run (), например:
public interface ITestRunner { void Run(); }
Затем создайте класс, который его реализует, например:
public class TestRunner : ITestRunner { public void Run() { // implementation goes here } }
Измените имя RulesEngine на что-то вроде TestHarness и установите свои свойства:
private TestHarness<ITestRunner> testHarness; public TestHarness<ITestRunner> TestHarness { get { if (null == testHarness) { string sourcePath = Path.Combine(Application.StartupPath, "TestRunner.cs"); testHarness = new TestHarness<ITestRunner>(sourcePath , typeof(TestRunner).FullName); } return testHarness; } } public ITestRunner TestHarnessInterface { get { return TestHarness.Interface; } }
Затем, где бы вы ни захотели его вызвать, вы можете просто запустить:
ITestRunner testRunner = TestHarnessInterface; if (null != testRunner) { testRunner.Run(); }
Это, вероятно, отлично подойдет для системы плагинов, но мой код как есть ограничен загрузкой и запуском одного файла, поскольку все наши правила находятся в одном исходном файле C #. Я бы подумал, что было бы довольно легко изменить его, просто передав тип / исходный файл для каждого из них, который вы хотите запустить. Вам просто нужно переместить код из получателя в метод, который принимает эти два параметра.
Кроме того, используйте IRunnable вместо ITestRunner.
источник
Вам нужно будет использовать отражение, чтобы получить тип TestRunner. Используйте метод Assembly.GetType.
class Program { static void Main(string[] args) { Assembly assembly = Assembly.LoadFile(@"C:\dyn.dll"); Type type = assembly.GetType("TestRunner"); var obj = (TestRunner)Activator.CreateInstance(type); obj.Run(); } }
источник
MethodInfo
тип и вызовInvoke
? (Я понял исходный вопрос как указание на то, что вызывающий абонент ничего не знает о рассматриваемомКогда вы создаете свою сборку, вы можете вызвать
AssemblyBuilder.SetEntryPoint
, а затем получить ее обратно изAssembly.EntryPoint
свойства, чтобы вызвать ее.Имейте в виду, что вы захотите использовать эту подпись, и обратите внимание, что ее имя необязательно
Main
:static void Run(string[] args)
источник