【问题标题】:Invoke Method, pass object as type调用方法,将对象作为类型传递
【发布时间】:2019-05-10 12:36:12
【问题描述】:

我正在使用以下课程:

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

并包含一个名为“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,我可以确定整个问题。总体问题是,当前的应用程序域不允许直接从其他应用程序域加载加载程序集。就像亚当指出的那样,有三种解决方案(在链接的微软文章中)。第三个也是最容易实现的解决方案是使用 AssemblyResolve 事件。虽然这是一个很好的解决方案,但让我的应用程序遇到异常来解决这个问题让我心痛。

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

仅从 exe 所在的文件夹加载程序集可以解决 FileNotFound 和 evil twin 错误:

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

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

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

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

【问题讨论】:

  • "...不幸的是,这总是崩溃..." - 您应该发布实际的异常消息。此外,在这种情况下,提及 WPF 并不真正相关
  • 还不清楚为什么需要 classObject,因为您正在调用静态方法。 Invoke 的第一个参数被忽略。
  • 你使用的GetMethod()重载只能找到实例方法。你需要 BindingFlags.Public |绑定标志。静态。并将 null 作为第一个参数传递给 Invoke(),因为它是静态的。
  • 简单的解释是你忘记在你的问题中好好记录一下:这两个类存在于不同的程序集中。并且即时编译器找不到包含“Person”的那个,从而产生 FileNotFoundException。完全猜测,根本没有问题或代码 sn-p 的支持,但问题符合事故。使用 Fuslogvw.exe 解决程序集解析问题。

标签: c# .net reflection invoke


【解决方案1】:

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

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

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

parameters.ReferencedAssemblies.Add("some_dll");

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

【讨论】:

  • 不幸的是,任何地方都没有其他例外。虽然上面的代码有一个小错误。看看我的编辑。
  • 目前我不清楚以前的错误是什么,现在的问题是什么。你能详细说明一下吗?
  • 和以前一样的错误,没有任何改变。通过将组件添加到解决方案并直接声明 Person 代码可以工作。问题是它确实无法使用 Activator.CreateInstance(t);
  • @colosso 我不明白你为什么需要使用 Activator.CreateInstance。你可以解释吗?另外,您能否发布一个最小的工作示例来说明您的问题?
  • 谢谢。您现在可以按照另一个答案中的说明使用 AssemblyResolve 并且它可以工作。但是你必须在你的代码中动态加载这个程序集吗?如果您添加常规引用并正常初始化 Person ,则无需 AssemblyResolve 技巧即可工作。
【解决方案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-in-a-folder-that-is 方法3(使用AssemblyResolve 事件)。

我必须承认,因为您已向程序集添加了 ref,为什么它不能正常工作。

我应该补充一点,将定义 Person 的额外 dll 复制到您的 exe 目录中将不起作用,因为您会遇到“邪恶双胞胎”问题,即在一个程序集中创建的类型不能被该程序集的另一个实例使用。 (你得到的错误是令人费解的"System.ArgumentException: 'Object of type 'Person' cannot be converted to type 'Person'."!!)

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

【讨论】:

  • 非常感谢您的帮助。如果你有兴趣,看看我上面最后做了什么!赏金将随之而来。 :)
  • @colosso - 完全同意从 exe 目录加载是最好的方法。不久前,当我们从多个文件夹加载 dll 时,我们遇到了“邪恶双胞胎”问题。哦,我们玩得很开心......无论如何,很高兴我能在正确的方向上稍微推动一下:-)
猜你喜欢
  • 2022-01-22
  • 1970-01-01
  • 2013-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多