簡體   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