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

时间:2019-05-10 12:36:12

标签: c# .net reflection invoke

我正在上以下课:

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) 仅将“对象”用作参数类型是可行的,但在我看来,这是不可能的。

有没有一种方法可以接收类型正确的对象?还是maby告诉Invocation方法类型匹配?

编辑:

猜猜我在上面的代码和测试中都犯了一个错误: 如前所述声明人(现在已在上面进行评论)可以正常工作:

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

使用Activator.Createinstance(现在在上面已更正)来动态创建Assebly的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,我可以确定整个问题。总体问题是,当前的appdomain不允许直接从其他appdomain加载加载程序集。就像亚当指出的那样,有三种解决方案(在链接的Microsoft文章中)。第三个也是最容易实现的解决方案是使用AssemblyResolve事件。尽管这是一个很好的解决方案,但是让我的应用程序遇到异常来解决此问题让我很伤心。

就像亚当一样也指出,如果将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);

2 个答案:

答案 0 :(得分:2)

results.CompiledAssembly引发FileNotFoundException,因为由于生成过程中发生错误而未生成程序集。您可以通过检查CompilerResults的Errors属性来查看实际的编译错误。

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

您可以通过添加对包含Person类的程序集的引用来解决此问题:

parameters.ReferencedAssemblies.Add("some_dll");

编辑:我错过了评论,说参数包含对包含Person类的程序集的引用。这可能意味着result.Error集合中存在另一个错误。选中它,我将更新答案(由于没有50位代表,我无法发表评论。)

答案 1 :(得分:1)

这有效(至少也不例外):

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目录中将不起作用,因为您随后遇到“邪恶双胞胎”问题,其中在一个程序集中创建的类型不能被该程序集的另一个实例使用。 (您得到的错误是"System.ArgumentException: 'Object of type 'Person' cannot be converted to type 'Person'."!)