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

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, как упомянуто ранее (теперь прокомментировано выше), работает должным образом:

Person x = new Person();
x.Name = "Albert";
x.Age = 76;

Использование Activator.Createinstance (не корректно выше) для динамического создания Person 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 });
    }
}
}

И содержащий один класс Library Project, называемый "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; }
}

РЕДАКТИРОВАТЬ 3: с чем я закончил

Благодаря @ Adam Benson я смог определить всю проблему. Общая проблема заключается в том, что текущий домен приложения не позволяет напрямую загружать сборки из других доменов приложения. Как указал Адам, для этого есть три решения (в связанной статье Microsoft). Третье и, безусловно, самое простое решение для реализации - использование события AssemblyResolve. Хотя это хорошее решение, мне больно и больно, чтобы мое приложение работало с исключениями для решения этой проблемы.

Как Адам также указал, что вы получите еще одно исключение, если вы поместите dll прямо в папку, где находится исполняемый файл. Это только отчасти верно, поскольку ошибка злого двойника появляется, только если вы сравниваете Person из исходной сборки папки Debug и Person, загруженного из сборки appdomain (в основном, если у вас есть dll в обоих каталогах)

Загрузка сборки только из папки, в которой находится exe-файл, устраняет и FileNotFound, и ошибку злого двойника:

Old: System.IO.Path.GetFullPath(@"..\..\..\Person\bin\Debug\Person.dll");

Новые: System.IO.Path.GetFullPath(@"Person.dll");

В итоге я скопировал необходимую сборку в текущий рабочий каталог:

File.Copy(pathToAsseblyContainingPersonClass, currentDir + @"\\Person.dll" , true);
0
colosso 10 Май 2019 в 15:36

2 ответа

Лучший ответ

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

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- метод 3 in-a-folder-that-is (используйте событие AssemblyResolve).

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

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

Редактировать: только что обнаружил, что LoadFrom избегает загрузки одной и той же сборки дважды. Смотрите разницу между LoadFile и LoadFrom с .NET сборками?

1
Adam Benson 25 Июл 2019 в 10:00

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

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

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

parameters.ReferencedAssemblies.Add("some_dll");

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

2
kra 13 Май 2019 в 08:35