简体   繁体   English

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

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

Currently I am working on a project in C# where I have to implement reflection.目前我正在用 C# 开发一个项目,我必须在其中实现反射。 I have created a WPF application with a GUI.我创建了一个带有 GUI 的 WPF 应用程序。 This GUI contains a combobox which contains all the classnames that implement a specific interface.此 GUI 包含一个组合框,其中包含实现特定接口的所有类名。 The classes with the displayed classnames live in the same solution.具有显示类名的类存在于同一个解决方案中。 Next to the combobox is a button to refresh the content in the combobox.组合框旁边是一个按钮,用于刷新组合框中的内容。 However, when I run my application, modify a classname that implements the interface, and click on that refresh button the changes don't show up in the combobox.但是,当我运行我的应用程序时,修改实现接口的类名,然后单击该刷新按钮,更改不会显示在组合框中。 For example, when I change a classname it should display the new classname in stead of the old one.例如,当我更改一个类名时,它应该显示新的类名而不是旧的。

I have extracted this part of my project to test it in an empty console application.我已经提取了我项目的这一部分,以在一个空的控制台应用程序中对其进行测试。 Here I have an interface that is implemented by the classes QuickSortAlgorithm, DynamicSortAlgorithm and MergeSortAlgorithm.这里我有一个由 QuickSortAlgorithm、DynamicSortAlgorithm 和 MergeSortAlgorithm 类实现的接口。 Next I wrote the following, straight forward code, in my main class.接下来,我在主类中编写了以下直接代码。

    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);
        }
    }

When I run the application is see the classnames QuickSortAlgorithm, DynamicSortAlgorithm and MergeSortAlgorithm printed.当我运行应用程序时,会看到打印的类名 QuickSortAlgorithm、DynamicSortAlgorithm 和 MergeSortAlgorithm。 However if I change the name of the, for example, QuickSortAlgorithm class to QuickSortAlgorithmmmmm I would expect it to print QuickSortAlgorithmmmmm once I press a key.但是,如果我将 QuickSortAlgorithm 类的名称更改为 QuickSortAlgorithmmmmm,我希望它在我按下一个键后打印 QuickSortAlgorithmmmmm。 However this is not the case and the name QuickSortAlgorithm is still being displayed.然而,情况并非如此,名称 QuickSortAlgorithm 仍在显示。

I get the feeling that I overlook something in the concept of reflection.我觉得我忽略了反射概念中的某些东西。 Can this even be done after building the solution?这甚至可以在构建解决方案后完成吗? If I understood correctly this concept makes it possible to implement changes on runtime.如果我理解正确,这个概念可以在运行时实现更改。 I know that it will make my application much slower but I'm really eager to learn more about this concept.我知道这会使我的应用程序变慢,但我真的很想了解更多关于这个概念的信息。 If one can explain me what I'm doing wrong I would be very happy.如果有人能解释我做错了什么,我会很高兴。

That unfortunately does not work.不幸的是,这不起作用。 When your assembly gets loaded, it will stay loaded as it is, changes only applying when you restart your application.当您的程序集加载时,它将保持加载状态,更改仅在您重新启动应用程序时应用。

If you are using .NET Framework you can create a new AppDomain and load your assembly into this AppDomain.如果您使用 .NET Framework,您可以创建一个新的 AppDomain 并将您的程序集加载到此 AppDomain 中。 When you are done, you can unload the AppDomain and with it your assembly.完成后,您可以卸载 AppDomain 及其程序集。 That you can do multiple times in a running application.您可以在正在运行的应用程序中执行多次。

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
}

Be careful though, as working with AppDomains has caveats, such as the need to use remoting when communicating with the AppDomain.不过要小心,因为使用 AppDomains 有一些注意事项,例如在与 AppDomain 通信时需要使用远程处理。

In .NET Core there is no such way available, as far as I know据我所知,在 .NET Core 中没有这样的方法可用

Once you load a compiled .NET assembly into your application, you can't make further changes to the types in that assembly without restarting and rebuilding the application.将编译的 .NET 程序集加载到应用程序中后,如果不重新启动和重建应用程序,就无法对该程序集中的类型进行进一步更改。 If this were allowed, then it could lead to all kinds of weird behavior.如果这被允许,那么它可能会导致各种奇怪的行为。 For example, imagine if the application had a List<Foo> populated with 3 foos and then Foo.Id were changed from an int to a string .例如,假设应用程序有一个List<Foo>填充了 3 个 foos,然后Foo.Idint更改为string What should happen to that live data?那些实时数据应该怎么办?

However, if your application doing the reflecting is different than the assembly being reflected over, it is possible to set things up such that you can watch for changes to that assembly file and re-do your reflection.但是,如果您的应用程序进行反射与被反射的程序集不同,则可以进行设置,以便您可以观察对该程序集文件的更改并重新进行反射。 The key is to abandon System.Reflection (which only works on loaded assemblies) and instead use the Mono.Cecil library .关键是放弃 System.Reflection(它只适用于加载的程序集),而是使用Mono.Cecil 库

Cecil reads in the assembly metadata without loading the code into the application, so it works well for the "reflection-only" use-case. Cecil 读取程序集元数据而不将代码加载到应用程序中,因此它适用于“仅反射”用例。 Of course, what it can't do is actually invoke the code.当然,它不能做的实际上是调用代码。 The Cecil API contains many similarities to System.Reflection. Cecil API 包含许多与 System.Reflection 的相似之处。 For example:例如:

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();

Another note is that .NET Framework (not .NET Core) contains the notion of AppDomains which can be loaded an unloaded.另一个注意事项是 .NET Framework(不是 .NET Core)包含可以加载和卸载的 AppDomains 的概念。 These act like .NET "sub-processes" within the one process and have rules about what can cross their boundaries.它们就像一个进程中的 .NET“子进程”,并且有关于什么可以跨越它们的边界的规则。 If you really need to both reload code and execute it, then this could be a solution.如果您确实需要重新加载代码并执行它,那么这可能是一个解决方案。

Another option could be the Roslyn scripting API, which would work well if you want to dynamically load and execute source code (vs. compiled assemblies).另一种选择可能是 Roslyn 脚本 API,如果您想动态加载和执行源代码(与编译的程序集相比),它会很好地工作。

It looks like you're overlooking one small step: building your code.看起来您忽略了一小步:构建代码。 Once you rename the class to QuickSortAlgorithmmmm , you need to save and build that assembly.将类重命名为QuickSortAlgorithmmmm ,您需要保存并构建该程序集。

Doing so will recreate the assembly (assuming your application doesn't have an open handle on it).这样做将重新创建程序集(假设您的应用程序没有打开的句柄)。 After that, clicking the refresh button should show the new name.之后,单击刷新按钮应显示新名称。

If you can't reload the assembly because it has your GUI code in it too (which is running), you may want to separate out the classes that implement the interface into their own assembly, potentially build that separately, and copy it over into a directory where your app can find it (eg. in a Plugins directory).如果您无法重新加载程序集,因为它也包含您的 GUI 代码(正在运行),您可能希望将实现接口的类分离到它们自己的程序集中,可能单独构建,然后将其复制到您的应用程序可以找到它的目录(例如,在Plugins目录中)。

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

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