以编程方式使用已编译的 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。您可以改用 varobject,这样可以显着减少 开销(即使考虑装箱场景)。在使用反射的时候,应该尽量做到高效,因为反射本身就已经很昂贵了。

您当前正在调用 Login.ShowMessage 使用由

返回的 实例
dynamic L = AccessLogin.Invoke(null, null);
L.ShowMessage(""hi"");

当使用 实例 时,您将受制于编译的访问规则,即 Login.ShowMessageinternal(您知道 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"" });
            }
          }
        }";