Я работаю со следующим классом:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
И у меня есть строка, содержащая следующее:
public class PersonActions
{
public static void Greet(Person p)
{
string test = p.Name;
}
}
В моем клиентском приложении, разработанном в WPF (.NET 4.7), яя компилирую эту строку во время выполнения и вызываю метод Greet следующим образом:
//Person x = new Person();
//x.Name = "Albert";
//x.Age = 76;
var assembly = Assembly.LoadFile(pathToAsseblyContainingPersonClass);
Type t = assembly.GetType("Person");
var x = Activator.CreateInstance(t);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add(pathToAsseblyContainingPersonClass);
//code being the code from abrom above (PersonActions)
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
Assembly importassembly = results.CompiledAssembly;
Type assemblytype = importassembly.GetType("PersonActions");
ConstructorInfo constructor = assemblytype.GetConstructor(Type.EmptyTypes);
object classObject = constructor.Invoke(new object[] { });// not used for anything
MethodInfo main = assemblytype.GetMethod("Greet");
main.Invoke(classObject, new object[] { x });
К сожалению, это всегда происходит сбой, потому что каким-то образом он не может найти метод с тем же типом параметра, даже если типы происходят из одной и той же сборки.
Выдается сообщение об ошибке «System.IO.FileNotFoundException», хотя в этом нет особого смысла.Это не файл, который не может быть найден, это перегрузка метода.
Каким-то образом он просто ищет: public static void Greet(object p)
Использование только «объекта» в качестве типа параметра работает, но в моем случае это невозможно.
Есть ли способ получить объект того типа, которым он является?Или, может, сказать методу Invocation, что типы совпадают?
РЕДАКТИРОВАТЬ:
Думаю, я допустил как ошибку в моем коде выше, так и в моих тестах: Объявление человека, как упоминалось ранее (теперь прокомментировано выше)) работает правильно:
Person x = new Person();
x.Name = "Albert";
x.Age = 76;
Использование Activator.Createinstance (теперь исправлено выше) для создания персонажа x динамически формирует сборку, не работает.Кажется, что var x = Activator.CreateInstance(t);
заставляет x все еще быть "объектом", а не "человеком".
РЕДАКТИРОВАТЬ 2: Вот минимальный рабочий пример проблемы:
Наличие решения, содержащегоодно приложение WPF.MainWindow.cs, содержащий:
using Microsoft.CSharp;
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Example
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
string code = @"public class PersonActions
{
public static void Greet(Person p)
{
}
}";
//Change to an absolute path if there is an exception
string pathToAsseblyContainingPersonClass = System.IO.Path.GetFullPath(@"..\..\..\Person\bin\Debug\Person.dll");
var assembly = Assembly.LoadFile(pathToAsseblyContainingPersonClass);
Type t = assembly.GetType("Person");
var x = Activator.CreateInstance(t);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add(pathToAsseblyContainingPersonClass);
//code being the code from abrom above (PersonActions)
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
Assembly importassembly = results.CompiledAssembly;
Type assemblytype = importassembly.GetType("PersonActions");
ConstructorInfo constructor = assemblytype.GetConstructor(Type.EmptyTypes);
object classObject = constructor.Invoke(new object[] { });// not used for anything
MethodInfo main = assemblytype.GetMethod("Greet");
main.Invoke(classObject, new object[] { x });
}
}
}
и содержащий один класс Библиотечный проект, называемый "Person", содержащий: (обратите внимание, что нет пространства имен)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
EDIT 3: Что у меня получилось
Благодаря @ Adam Benson я смог определить всю проблему.Общая проблема заключается в том, что текущий домен приложения не позволяет напрямую загружать сборки из других доменов приложения.Как указал Адам, для этого есть три решения (в связанной статье Microsoft).Третье и, безусловно, самое простое решение для реализации - использование события AssemblyResolve.Хотя это хорошее решение, мне больно и душевно, чтобы мое приложение работало с исключениями для решения этой проблемы.
Как Адам также указал, что вы получаете еще одно исключение, если вы помещаете dll прямо в папку, где находится исполняемый файл.Это только отчасти верно, поскольку ошибка злого двойника появляется, только если вы сравниваете Person из исходной сборки папки Debug и Person, загруженного из сборки appdomain (в основном, если у вас есть dll в обоих каталогах)
Загрузкасборка только из папки, в которой находится exe-файл, устраняет и FileNotFound, и ошибку злого двойника:
old: System.IO.Path.GetFullPath(@"..\..\..\Person\bin\Debug\Person.dll");
new: System.IO.Path.GetFullPath(@"Person.dll");
Так чтоЯ закончил тем, что сначала скопировал необходимую сборку в текущий рабочий каталог:
File.Copy(pathToAsseblyContainingPersonClass, currentDir + @"\\Person.dll" , true);