调用方法,将对象作为类型传递

Invoke Method, pass object as type

我正在使用以下 class:

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) 仅使用 'object' 作为参数类型是可行的,但在我的情况下不可能。

有没有办法接收对象的类型?或者可以告诉调用方法类型匹配?

编辑:

猜猜我在上面的代码和测试中都犯了错误: 如前所述(现在在上面评论)声明 Person 正常工作:

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

使用 Activator.Createinstance(现在上面正确)从组件动态创建 Person x 是行不通的。好像是var x = Activator.CreateInstance(t); 导致 x 仍然是 "object" 而不是 "Person".

编辑 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 });
    }
}
}

并且包含一个 class 库项目调用 "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,我可以找出整个问题。总体问题是,当前的应用程序域不允许直接从其他应用程序域加载程序集。就像 Adam 指出的那样,有三种解决方案(在链接的 Microsoft 文章中)。第三个也是最容易实现的解决方案是使用 AssemblyResolve 事件。虽然这是一个很好的解决方案,但让我的应用程序 运行 进入异常来解决这个问题让我心痛。

正如Adam 也指出的那样,如果将dll 直接放入exe 所在的文件夹,则会出现另一个异常。这只是部分正确,因为仅当您比较原始 Debug 文件夹程序集的 Person 和从 appdomain 程序集加载的 Person 时才会出现邪恶的双胞胎错误(基本上如果您在两个目录中都有 dll)

仅从 exe 所在的文件夹加载程序集解决了 FileNotFound 和邪恶双胞胎错误:

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

新:System.IO.Path.GetFullPath(@"Person.dll");

所以我最终做的是首先将必要的程序集复制到当前工作目录中:

File.Copy(pathToAsseblyContainingPersonClass, currentDir + @"\Person.dll" , true);

results.CompiledAssembly 抛出 FileNotFoundException,因为由于生成过程中发生错误而未生成程序集。您可以通过检查 CompilerResults.

的 Errors 属性 来查看实际的编译错误

在这种情况下,错误是提供给 CompileAssemblyFromSource 的代码不知道 Person class 是什么。

您可以通过添加对包含 Person class:

的程序集的引用来解决此问题
parameters.ReferencedAssemblies.Add("some_dll");

编辑: 我错过了评论说参数包含对包含 Person class 的程序集的引用。这可能意味着 results.Error 集合中存在不同的错误。检查它,我会更新答案(由于没有 50 个代表,我还不能发表评论)。

这行得通(至少没有例外):

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 事件)。

我必须承认,我很困惑为什么它不能正常工作,因为您已经向程序集添加了一个引用。

我应该补充一点,将定义 Person 的额外 dll 复制到您的 exe 目录将无法正常工作,然后 运行 进入 "evil twin" 问题,其中在一个程序集中创建的类型不能被使用该程序集的另一个实例。 (你得到的错误是 mind-bending "System.ArgumentException: 'Object of type 'Person' cannot be converted to type 'Person'."!!)

编辑:刚刚发现 LoadFrom 避免加载同一个程序集两次。参见 Difference between LoadFile and LoadFrom with .NET Assemblies?