繁体   English   中英

C# 反射 - 如何在运行时重新加载类?

[英]C# Reflection - How to reload a class on runtime?

目前我正在用 C# 开发一个项目,我必须在其中实现反射。 我创建了一个带有 GUI 的 WPF 应用程序。 此 GUI 包含一个组合框,其中包含实现特定接口的所有类名。 具有显示类名的类存在于同一个解决方案中。 组合框旁边是一个按钮,用于刷新组合框中的内容。 但是,当我运行我的应用程序时,修改实现接口的类名,然后单击该刷新按钮,更改不会显示在组合框中。 例如,当我更改一个类名时,它应该显示新的类名而不是旧的。

我已经提取了我项目的这一部分,以在一个空的控制台应用程序中对其进行测试。 这里我有一个由 QuickSortAlgorithm、DynamicSortAlgorithm 和 MergeSortAlgorithm 类实现的接口。 接下来,我在主类中编写了以下直接代码。

    public static List<string> AlgoList = new List<string>();

    static void Main(string[] args) {
        RefreshAlgorithms();
        Print();

        Console.WriteLine("\nChange a classname and press a key \n");
        Console.ReadKey();

        Print();

        Console.WriteLine("\nPress a key to exit the program \n");
        Console.ReadKey();
    }

    private static void RefreshAlgorithms() {
        AlgoList.Clear();
        Type AlgorithmTypes = typeof(IAlgorithms);
        foreach (var type in Assembly.GetCallingAssembly().GetTypes()) {
            if (AlgorithmTypes.IsAssignableFrom(type) && (type != AlgorithmTypes)) {
                AlgoList.Add(type.Name);
            }
        }
    }

    private static void Print() {
        Console.WriteLine("Algorithm classes:");
        foreach (var Algo in AlgoList) {
            Console.WriteLine(Algo);
        }
    }

当我运行应用程序时,会看到打印的类名 QuickSortAlgorithm、DynamicSortAlgorithm 和 MergeSortAlgorithm。 但是,如果我将 QuickSortAlgorithm 类的名称更改为 QuickSortAlgorithmmmmm,我希望它在我按下一个键后打印 QuickSortAlgorithmmmmm。 然而,情况并非如此,名称 QuickSortAlgorithm 仍在显示。

我觉得我忽略了反射概念中的某些东西。 这甚至可以在构建解决方案后完成吗? 如果我理解正确,这个概念可以在运行时实现更改。 我知道这会使我的应用程序变慢,但我真的很想了解更多关于这个概念的信息。 如果有人能解释我做错了什么,我会很高兴。

不幸的是,这不起作用。 当您的程序集加载时,它将保持加载状态,更改仅在您重新启动应用程序时应用。

如果您使用 .NET Framework,您可以创建一个新的 AppDomain 并将您的程序集加载到此 AppDomain 中。 完成后,您可以卸载 AppDomain 及其程序集。 您可以在正在运行的应用程序中执行多次。

void RefreshAlgorithms()
{
    var appDomain = AppDomain.CreateDomain("TempDomain");
    appDomain.Load(YourAssembly);
    appDomain.DoCallback(Inspect);
    AppDomain.Unload(appDomain);
}

void Inspect()
{
    // This runs in the new appdomain where you can inspect your classes
}

不过要小心,因为使用 AppDomains 有一些注意事项,例如在与 AppDomain 通信时需要使用远程处理。

据我所知,在 .NET Core 中没有这样的方法可用

将编译的 .NET 程序集加载到应用程序中后,如果不重新启动和重建应用程序,就无法对该程序集中的类型进行进一步更改。 如果这被允许,那么它可能会导致各种奇怪的行为。 例如,假设应用程序有一个List<Foo>填充了 3 个 foos,然后Foo.Idint更改为string 那些实时数据应该怎么办?

但是,如果您的应用程序进行反射与被反射的程序集不同,则可以进行设置,以便您可以观察对该程序集文件的更改并重新进行反射。 关键是放弃 System.Reflection(它只适用于加载的程序集),而是使用Mono.Cecil 库

Cecil 读取程序集元数据而不将代码加载到应用程序中,因此它适用于“仅反射”用例。 当然,它不能做的实际上是调用代码。 Cecil API 包含许多与 System.Reflection 的相似之处。 例如:

var assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly(Path.Combine(projectDirectory, "bin", "Debug", "Something.dll"));
var controllerTypes = assembly.MainModule.Types.Where(t => t.BaseType?.FullName == "System.Web.Mvc.Controller")
    .ToArray();

另一个注意事项是 .NET Framework(不是 .NET Core)包含可以加载和卸载的 AppDomains 的概念。 它们就像一个进程中的 .NET“子进程”,并且有关于什么可以跨越它们的边界的规则。 如果您确实需要重新加载代码并执行它,那么这可能是一个解决方案。

另一种选择可能是 Roslyn 脚本 API,如果您想动态加载和执行源代码(与编译的程序集相比),它会很好地工作。

看起来您忽略了一小步:构建代码。 将类重命名为QuickSortAlgorithmmmm ,您需要保存并构建该程序集。

这样做将重新创建程序集(假设您的应用程序没有打开的句柄)。 之后,单击刷新按钮应显示新名称。

如果您无法重新加载程序集,因为它也包含您的 GUI 代码(正在运行),您可能希望将实现接口的类分离到它们自己的程序集中,可能单独构建,然后将其复制到您的应用程序可以找到它的目录(例如,在Plugins目录中)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM