问题
Currently I am working on a project in C# where I have to implement reflection. I have created a WPF application with a GUI. This GUI contains a combobox which contains all the classnames that implement a specific interface. 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. 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. 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. However this is not the case and the name QuickSortAlgorithm is still being displayed.
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.
回答1:
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. When you are done, you can unload the AppDomain and with it your assembly. 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.
In .NET Core there is no such way available, as far as I know
回答2:
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. 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
. 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.
Cecil reads in the assembly metadata without loading the code into the application, so it works well for the "reflection-only" use-case. Of course, what it can't do is actually invoke the code. The Cecil API contains many similarities to 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. These act like .NET "sub-processes" within the one process and have rules about what can cross their boundaries. 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).
回答3:
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.
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).
来源:https://stackoverflow.com/questions/55503651/c-sharp-reflection-how-to-reload-a-class-on-runtime