Вызвать метод, передать объект как тип - PullRequest
0 голосов
/ 10 мая 2019

Я работаю со следующим классом:

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);

Ответы [ 2 ]

2 голосов
/ 13 мая 2019

results.CompiledAssembly создает исключение FileNotFoundException, поскольку сборка не создается из-за ошибки, возникшей в процессе создания.Вы можете увидеть фактическую ошибку компиляции, проверив свойство Errors CompilerResults.

В этом случае ошибка заключается в том, что код, предоставленный для CompileAssemblyFromSource, не знает, что такое класс Person.

Вы можете это исправитьдобавив ссылку на сборку, содержащую класс Person:

parameters.ReferencedAssemblies.Add("some_dll");

Edit: Я пропустил комментарий о том, что параметры содержат ссылку на сборку, содержащую класс Person.Это, вероятно, означает, что в результатах имеется другая ошибка. Коллекция ошибок.Проверьте его, и я обновлю ответ (я пока не могу комментировать из-за отсутствия 50 повторений).

1 голос
/ 13 мая 2019

Это работает (по крайней мере, не исключение):

object classObject = constructor.Invoke(new object[] { });// not used for anything

//////////////////////////////////////////

AppDomain.CurrentDomain.AssemblyResolve +=
    (object sender, ResolveEventArgs resolve_args) =>
    {
        if (resolve_args.Name == assembly.FullName)
            return assembly;
        return null;
    };

//////////////////////////////////////////

MethodInfo main = assemblytype.GetMethod("Greet");

На основе https://support.microsoft.com/en-gb/help/837908/how-to-load-an-assembly-at-runtime-that-is-located-in-a-folder-that-is метода 3 (используйте событие AssemblyResolve).

Я должен признаться, что был озадачен тем, почему это не работает, так как вы добавили ссылку на сборку.

Я должен добавить, что копирование дополнительной dll, определяющей Person, в ваш exe-каталог не будет работать, так как вы столкнетесь с проблемой «злой близнец», когда тип, созданный в одной сборке, не может использоваться другим экземпляром этой сборки. (Ошибка, которую вы получаете - это умопомрачительный "System.ArgumentException: 'Object of type 'Person' cannot be converted to type 'Person'." !!)

...