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

问题描述 投票:1回答:3

目前我正在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。

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

c# reflection .net-assembly
3个回答
1
投票

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

如果您使用的是.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中没有这样的方法可用


1
投票

将已编译的.NET程序集加载到应用程序后,无法在不重新启动和重新构建应用程序的情况下对该程序集中的类型进行进一步更改。如果允许这样做,那么它可能导致各种奇怪的行为。例如,想象一下,如果应用程序有一个List<Foo>填充3 foos然后Foo.Idint变为string。该实时数据会发生什么?

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

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,如果您想动态加载和执行源代码(与已编译的程序集相比),它可以很好地工作。


0
投票

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.