调用方法,将对象作为类型传递
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?
我正在使用以下 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?