以编程方式使用已编译的 C# 时如何访问当前命名空间内的方法?
how to access to a method inside current namespace when using a compiled C# programmatically?
在查看了所有关于以编程方式编译 C# 代码的答案和问题后,我选择了这种方法:
CompileCSCAtRuntime
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using AA.UI.WPF.WND;
namespace AA.UI.WPF.COMMON
{
public static class CompileCSCAtRuntime
{
public static void HelloWorld()
{
string code = @"
using System;
using System.Windows;
using System.Windows.Forms;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var aassembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type CompileCSCAtRuntime = aassembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
Type Login = aassembly.GetType(""AA.UI.WPF.WND.Login"");
MethodInfo AccessLogin = CompileCSCAtRuntime.GetMethod(""AccessLogin"");
dynamic L = AccessLogin.Invoke(null, null);
L.ShowMessage(""hi"");
}
}
}
";
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
string[] refr = {
"System",
"System.Windows",
"System.Windows.Forms",
"System.Drawing",
"Microsoft.CSharp",
"System.Core",
"System.Data"};
foreach (string r in refr)
parameters.ReferencedAssemblies.Add($"{r}.dll");
parameters.ReferencedAssemblies.Add($"Path_To_Assembly");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = true;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.Count > 0)
{
MessageBox.Show(
results.Errors
.Cast<CompilerError>()
.Select(error => error.ErrorText)
.Aggregate((s, s1) => s += $";\r\n\r\n{s1}"));
return;
}
Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("AA.UI.WPF.COMMON.Program");
MethodInfo main = program.GetMethod("Main");
main.Invoke(null, null);
}
public static Login AccessLogin()
{
return Login.Instance;
}
}
}
和登录
using System;
using AA.UI.WPF.COMMON;
namespace AA.UI.WPF.WND
{
public partial class Login
{
internal static Login Instance => _instance ?? (_instance = new Login());
private static Login _instance = null;
public Login()
{
InitializeComponent();
if (_instance == null) _instance = this;
}
internal void ShowMessage(string msg)
{
MessageBox.Show(msg);
}
}
更新
如果我不使用反射,它工作正常。
在编辑之前,我问我如何才能访问动态编译的 c# 代码之外的方法,我对 @BionicCode 的回答感到满意。谢谢他。看评论。
你的回答完全正确。自从你 post 第一个回答我就说你这是真的。我按照你说的使用动态类型。
但最后一件事,我无法访问私有或内部方法,RuntimeBinderException: 'AA.UI.WPF.WND.Login.ShowMessage(string)' is inaccessible due to its protection level
。我觉得很正常
目标
我的目标是将一些代码作为字符串注入,并且 运行 这些代码就像其他没有反射的普通代码一样,因为它太复杂并且可以访问其他 类、类型等...在当前命名空间 FD.UI.WPF。如果您知道其他更简单的方法,请提供。
就像引用其他程序集一样,您必须创建并引用自己的程序集。
- 使用新的 class 创建另一个 C# 项目
AssemblyOfMessageHelper
,例如 MessageHelper
,其中包含 ShowMessage
.
- 然后在你的动态编译代码中,调用
MessageHelper.ShowMessage
.
- 确保它有
using NamespaceOfMessageHelper;
和
- 您引用程序集
parameters.ReferencedAssemblies.Add("AssemblyOfMessageHelper.dll");
。
- 请记住,程序集需要位于可以找到它的文件夹中,因为
System.*
程序集位于 GAC(.NET 框架)或适当的 SDK 框架位置(.NET核心), 而这两个都不会有你的 AssemblyOfMessageHelper.dll
.
UPDATE:正如其他人所指出的,您可以通过引用您自己的程序集来进一步简化它:parameters.ReferencedAssemblies.Add("MainProject.exe");
但是,我仍然认为创建一个单独的程序集是首选方式。
与往常一样,如果知道您收到的错误消息,那将非常有趣。我假设您遇到了编译器错误?
但通常您应该能够执行来自其他程序集的方法。
根据您的代码,您必须添加对 CompilerParameters
的正确引用,这是包含您希望引用的 CompileCSCAtRuntime
类型的程序集,例如 AA.UI.WPF.COMMON.dll
(我此时不知道真正的程序集名称):
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Windows.dll");
parameters.ReferencedAssemblies.Add("AA.UI.WPF.COMMON.dll");
现在您可以使用以下代码(注意添加的 using
导入):
string code = @"
using System;
using System.Windows;
using AA.UI.WPF.COMMON;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
CompileCSCAtRuntime.ShowMessage(""hello"");
}
}
}";
为了增加灵活性,您必须动态加载包含该类型的程序集,然后使用 MethodInfo
(反射)执行该方法。这样您就不需要添加任何程序集引用或 using
导入到编译代码:
string code = @"
using System;
using System.Windows;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var assembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type type = assembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
MethodInfo method = type.GetMethod(""ShowMessage"");
method.Invoke(null, new[] { ""hello"" });
}
}
}";
您可以使用此代码段来输出或记录编译器错误:
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.Count > 0)
{
MessageBox.Show(
results.Errors
.Cast<CompilerError>()
.Select(error => error.ErrorText)
.Aggregate((result, currentValue) => result += $";{Environment.NewLine}{currentValue}"));
}
处理您的更新
现在,上下文已经改变,您必须将您的代码采用到这个新场景中。首先这里不需要dynamic
。您可以改用 var
或 object
,这样可以显着减少 开销(即使考虑装箱场景)。在使用反射的时候,应该尽量做到高效,因为反射本身就已经很昂贵了。
您当前正在调用 Login.ShowMessage
使用由
返回的 实例
dynamic L = AccessLogin.Invoke(null, null);
L.ShowMessage(""hi"");
当使用 实例 时,您将受制于编译的访问规则,即 Login.ShowMessage
是 internal
(您知道 internal
限制对程序集作用域的访问)。
由于您的动态编译代码被编译成一个新的动态程序集,调用者的范围(动态代码)不再满足此访问约束。
要绕过此可见性限制,您必须使用反射访问和调用非 public 成员。要获得任何非 public 成员的 MemberInfo
,您始终必须指定适当的 BindingFlags
组合:
string code = @"
using System;
using System.Windows;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var assembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type compileCSCAtRuntimeType = assembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
MethodInfo accessLoginMethod = compileCSCAtRuntimeType.GetMethod(""AccessLogin"");
object resultInstance = accessLoginMethod.Invoke(null, null);
Type resultType = resultInstance.GetType();
MethodInfo resultMethod = resultType.GetMethod(""ShowMessage"",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
resultMethod.Invoke(resultInstance, new [] { ""hi"" });
}
}
}";
在查看了所有关于以编程方式编译 C# 代码的答案和问题后,我选择了这种方法:
CompileCSCAtRuntime
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
using AA.UI.WPF.WND;
namespace AA.UI.WPF.COMMON
{
public static class CompileCSCAtRuntime
{
public static void HelloWorld()
{
string code = @"
using System;
using System.Windows;
using System.Windows.Forms;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var aassembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type CompileCSCAtRuntime = aassembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
Type Login = aassembly.GetType(""AA.UI.WPF.WND.Login"");
MethodInfo AccessLogin = CompileCSCAtRuntime.GetMethod(""AccessLogin"");
dynamic L = AccessLogin.Invoke(null, null);
L.ShowMessage(""hi"");
}
}
}
";
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
string[] refr = {
"System",
"System.Windows",
"System.Windows.Forms",
"System.Drawing",
"Microsoft.CSharp",
"System.Core",
"System.Data"};
foreach (string r in refr)
parameters.ReferencedAssemblies.Add($"{r}.dll");
parameters.ReferencedAssemblies.Add($"Path_To_Assembly");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = true;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.Count > 0)
{
MessageBox.Show(
results.Errors
.Cast<CompilerError>()
.Select(error => error.ErrorText)
.Aggregate((s, s1) => s += $";\r\n\r\n{s1}"));
return;
}
Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("AA.UI.WPF.COMMON.Program");
MethodInfo main = program.GetMethod("Main");
main.Invoke(null, null);
}
public static Login AccessLogin()
{
return Login.Instance;
}
}
}
和登录
using System;
using AA.UI.WPF.COMMON;
namespace AA.UI.WPF.WND
{
public partial class Login
{
internal static Login Instance => _instance ?? (_instance = new Login());
private static Login _instance = null;
public Login()
{
InitializeComponent();
if (_instance == null) _instance = this;
}
internal void ShowMessage(string msg)
{
MessageBox.Show(msg);
}
}
更新
如果我不使用反射,它工作正常。
在编辑之前,我问我如何才能访问动态编译的 c# 代码之外的方法,我对 @BionicCode 的回答感到满意。谢谢他。看评论。
你的回答完全正确。自从你 post 第一个回答我就说你这是真的。我按照你说的使用动态类型。
但最后一件事,我无法访问私有或内部方法,RuntimeBinderException: 'AA.UI.WPF.WND.Login.ShowMessage(string)' is inaccessible due to its protection level
。我觉得很正常
目标
我的目标是将一些代码作为字符串注入,并且 运行 这些代码就像其他没有反射的普通代码一样,因为它太复杂并且可以访问其他 类、类型等...在当前命名空间 FD.UI.WPF。如果您知道其他更简单的方法,请提供。
就像引用其他程序集一样,您必须创建并引用自己的程序集。
- 使用新的 class 创建另一个 C# 项目
AssemblyOfMessageHelper
,例如MessageHelper
,其中包含ShowMessage
. - 然后在你的动态编译代码中,调用
MessageHelper.ShowMessage
. - 确保它有
using NamespaceOfMessageHelper;
和 - 您引用程序集
parameters.ReferencedAssemblies.Add("AssemblyOfMessageHelper.dll");
。 - 请记住,程序集需要位于可以找到它的文件夹中,因为
System.*
程序集位于 GAC(.NET 框架)或适当的 SDK 框架位置(.NET核心), 而这两个都不会有你的AssemblyOfMessageHelper.dll
.
UPDATE:正如其他人所指出的,您可以通过引用您自己的程序集来进一步简化它:parameters.ReferencedAssemblies.Add("MainProject.exe");
但是,我仍然认为创建一个单独的程序集是首选方式。
与往常一样,如果知道您收到的错误消息,那将非常有趣。我假设您遇到了编译器错误?
但通常您应该能够执行来自其他程序集的方法。
根据您的代码,您必须添加对 CompilerParameters
的正确引用,这是包含您希望引用的 CompileCSCAtRuntime
类型的程序集,例如 AA.UI.WPF.COMMON.dll
(我此时不知道真正的程序集名称):
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Windows.dll");
parameters.ReferencedAssemblies.Add("AA.UI.WPF.COMMON.dll");
现在您可以使用以下代码(注意添加的 using
导入):
string code = @"
using System;
using System.Windows;
using AA.UI.WPF.COMMON;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
CompileCSCAtRuntime.ShowMessage(""hello"");
}
}
}";
为了增加灵活性,您必须动态加载包含该类型的程序集,然后使用 MethodInfo
(反射)执行该方法。这样您就不需要添加任何程序集引用或 using
导入到编译代码:
string code = @"
using System;
using System.Windows;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var assembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type type = assembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
MethodInfo method = type.GetMethod(""ShowMessage"");
method.Invoke(null, new[] { ""hello"" });
}
}
}";
您可以使用此代码段来输出或记录编译器错误:
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.Count > 0)
{
MessageBox.Show(
results.Errors
.Cast<CompilerError>()
.Select(error => error.ErrorText)
.Aggregate((result, currentValue) => result += $";{Environment.NewLine}{currentValue}"));
}
处理您的更新
现在,上下文已经改变,您必须将您的代码采用到这个新场景中。首先这里不需要dynamic
。您可以改用 var
或 object
,这样可以显着减少 开销(即使考虑装箱场景)。在使用反射的时候,应该尽量做到高效,因为反射本身就已经很昂贵了。
您当前正在调用 Login.ShowMessage
使用由
dynamic L = AccessLogin.Invoke(null, null);
L.ShowMessage(""hi"");
当使用 实例 时,您将受制于编译的访问规则,即 Login.ShowMessage
是 internal
(您知道 internal
限制对程序集作用域的访问)。
由于您的动态编译代码被编译成一个新的动态程序集,调用者的范围(动态代码)不再满足此访问约束。
要绕过此可见性限制,您必须使用反射访问和调用非 public 成员。要获得任何非 public 成员的 MemberInfo
,您始终必须指定适当的 BindingFlags
组合:
string code = @"
using System;
using System.Windows;
using System.Reflection;
namespace AA.UI.WPF.COMMON
{
public class Program
{
public static void Main()
{
var assembly = Assembly.LoadFrom(@""Path_To_Assembly"");
Type compileCSCAtRuntimeType = assembly.GetType(""AA.UI.WPF.COMMON.CompileCSCAtRuntime"");
MethodInfo accessLoginMethod = compileCSCAtRuntimeType.GetMethod(""AccessLogin"");
object resultInstance = accessLoginMethod.Invoke(null, null);
Type resultType = resultInstance.GetType();
MethodInfo resultMethod = resultType.GetMethod(""ShowMessage"",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
resultMethod.Invoke(resultInstance, new [] { ""hi"" });
}
}
}";