Showing posts with label Reflection. Show all posts
Showing posts with label Reflection. Show all posts

Sunday, January 5, 2020

Using Type.GetType with .NET Core / Dynamically Loading .NET Standard Assemblies in .NET Core

The Type.GetType() method lets us load a type from the file system at runtime without needing compile time references. Because of the way that assemblies are loaded in .NET Core, this behaves differently than it did in .NET Framework.

Update Jan 13, 2020: The Type.GetType() method has not changed, but runtime behavior regarding assembly loading has changed. That doesn't negate the workaround shown here, so feel free to keep reading. For more information see the next article: Type.GetType() Functionality Has Not Changed in .NET Core.

Short Version
In .NET Framework, calling Type.GetType() with an assembly-qualified name...
  • Loads the specified assembly from the file system
    Note: "from the file system" is observed behavior, but technically not correct -- see the followup article mentioned above for more details. 
  • Returns a Type object
In .NET Core, calling Type.GetType() with an assembly-qualified name...
  • Does *NOT* load the specified assembly from the file system
  • If the assembly is not already loaded, GetType() returns null
  • If the assembly is already loaded, GetType() returns the Type object
This means that we need to take extra steps in .NET Core in order to use Type.GetType() with .NET Core applications. And things get more interesting if we are loading .NET Standard assemblies.

Note: The code for this article comes from my talk on C# interfaces (IEnumerable, ISaveable, IDontGetIt: Understanding .NET Interfaces). The code is in 2 separate GitHub repositories. The .NET Framework code is at GitHub: jeremybytes/understanding-interfaces. The .NET Core code is at GitHub: jeremybytes/understanding-interfaces-core30.
In the spirit of "the fastest way to get a right answer is to post a wrong one", I'm putting down my thought processes in solving an issue. If there is an easier way (such as setting a flag or adding a config setting), please let me know in the comments.
Why Dynamically Load Types?
I have used dynamic loading of types for 2 primary scenarios: (1) swapping one set of functionality for another, and (2) plugging in business rules. In either of these scenarios, we do not need to have the specifics of the dynamically loaded types available at compile time. Things can be figured out at run time.

In the first scenario (and the code sample we'll look at today), I have changed out data-access code from one system to another. For example, to get data from a SQL database, a web service, or some other location. This is particularly helpful when there are multiple clients using the same application with different data storage systems.

In the second scenario, I deployed an application with the option of adding or changing certain business rules at a later time. The business rules follow a specific interface and are stored in a separate assembly (or multiple assemblies). At runtime, the application loads the business rules from the file system. This made it easy to update existing rules, add rules, or remove rules without needing to recompile or redeploy the application; only the business rule files were affected by updates.

Dynamic Loading with .NET Framework
I ran across this issue when I was moving a WPF application from .NET Framework 4.7 to .NET Core 3.0. We'll start with code from the .NET Framework repository mentioned above (GitHub: jeremybytes/understanding-interfaces), specifically the "completed" code in the "04-DynamicLoading" project (completed/04-DynamicLoading).

This application dynamically loads a data repository that can get data from a text file (comman-separate values (CSV)), a web service (HTTP/JSON), or a SQL database (SQLite local db). The application does not know anything about these data repositories at compile-time. Instead, it loads a repository from the file system based on configuration.

Here is the code that dynamically loads the repository (from the RepositoryFactory.cs in the PeopleViewer project):


The "GetRepository" method returns IPersonRepository -- this is the interface that represents the data repository.

The first line of the method gets the assembly-qualified name from configuration. Here's the configuration section for the CSV Repository (from the App.config file of the PeopleViewer project):


This lists the assembly-qualified name for the repository. This consists of the following parts:
  • Fully-qualified Type Name: PersonRepository.CSV.CSVRepository
  • Assembly Name: PersonRepository.CSV (this is "PersonRepository.CSV.dll" on the file system)
  • Assembly Version: 1.0.0.0
  • Assembly Culture -- this is used for localization (which we haven't implemented here)
  • Assembly Public Key Token -- this is used for strongly-named assemblies (which we haven't implemented here)
The next line in our method makes a call to Type.GetType() with this assembly-qualified name. GetType will find the assembly on the file system (by default, it looks in the same folder as the executable). Then it loads the assembly and pulls out the information for the Type object.

Activator.CreateInstance will create an instance of the Type using a default constructor. In this case, it will be a CSVRepository.

The last 2 lines cast the new instance to the correct interface and return it.

Getting the Repository Assemblies
One other piece is that we need to get the CSVRepository assembly to the executable folder somehow. The project does not have any compile-time references to the assembly, so we need to do this manually.

We have the repositories (and all of their dependencies) in a folder at the solution level called "RepositoriesForBin" (you can look at the contents of RepositoriesForBin on GitHub). Here's a bit of a screenshot from Windows File Explorer:


This snippet shows the "PersonRepository.CSV.dll" file that we're using here. In addition, we have files for the web service repository, the SQL database repository, and all of the dependencies for those repositories.

To get these into the executable folder, the PeopleViewer project has a post-build step. (It's kind of buried in the PeopleViewer.csproj file on GitHub -- it's easier to look at this in the Visual Studio project properties). Here is the post-build section of the project properties:


This copies files from the from the "RepositoriesForBin" folder to the output folder (including any sub-folders).

For more information on build events, take a look at "Using Build Events in Visual Studio to Make Life Easier".

Running the Application
When we run the application, we get data using the CSV repository (which gets data from a text file on the file system).


Changing the Data Source
If you're curious about how the dynamic loading works. Shut down the application and open the executable file in File Explorer (this is in the PeopleViewer/bin/Debug folder).

Run "PeopleViewer.exe" by double-clicking it from File Explorer. You will see the same results as above.

Shut down the application, and then edit the "PeopleViewer.exe.config" file on the file system using your favorite text editor. Comment out the section for the "CSV Repository" and uncomment the section for "SQL Repository". Save and close the file.

Now when you re-run the application, it will use the SQL database instead of the text file.

In real life, we would not be doing this on a single machine. However, think of the scenario where we have multiple clients. For each client, we give them just the assemblies that they need for their particular environment. If a new client has a different data store, that's fine. We create a repository assembly and give it to that client. We do not need to recompile the application or deal with multiple versions deployed at different client sites.

Anyway, on to .NET Core.

Converting to .NET Core
For the .NET Framework project, the WPF application (PeopleViewer) is a WPF application. The web service (People.Service) is an ASP.NET Core 2.2 API. The interface project (PersonRepository.Interface) is a .NET Standard 2.0 project. All of the repository files are .NET Standard 2.0 as well.

Part of the reason for using .NET Standard project for many things is that I knew I would be moving the WPF application to .NET Core once .NET Core 3.0 was released. And that's what I did.

I moved the WPF application and the web service to .NET Core 3.0. And I moved the libraries (including the repositories) to .NET Standard 2.1.

One other thing I did with this application was change all references from "Repository" to "Reader". Since the operations for the repositories are read-only, the term "reader" is more appropriate.

The completed code is on GitHub (jeremybytes/understanding-interfaces-core30), specifically in the "completed/04-DynamicLoading" folder. Note that this repository has the completed code, so you won't be able to follow along with the interim code (you can contact me for details if you'd really like to follow along).

Broken Code in .NET Core
Unfortunately, if we take the "GetRepository" method (now called "GetReader") straight across, the code does not work.

If we run the application, we get an exception:


The "Activator.CreateInstance" method is giving us an ArgumentNullException. This means that the "readerType" variable that we have here is null.

"GetType" is not returning what we want. Here are the values of "readerTypeName" and the "readerType" in the debugger:


This shows that the "readerTypeName" variable is populated with the assembly-qualified name that we expect. So that's fine.

But the "readerType" that is returned from the GetType method is null.
GetType does not automatically load an assembly in .NET Core like it does in .NET Framework.
Frustration and Reasoning
This is where I went through a bit of frustration. When checking the documentation for "GetType", there is currently (as of Jan 5, 2020) no indication that it works differently. Here is a screenshot of the beginning of the "Remarks" section for Type.GetType (link (which will hopefully be updated by the time you read this): https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype?view=netcore-3.1#System_Type_GetType_System_String_):


Note that I do have ".NET Core 3.1" selected for the Version. Here is the start of the "Remarks" text:
"You can use the GetType method to obtain a Type object for a type in another assembly if you know its assembly-qualified name, which an be obtained from AssemblyQualifiedName. GetType causes loading of the assembly specified in typeName." (emphasis mine)
So, according to the documentation, this should work.

Assembly Loading and Unloading
The reason that this does not work is that the assembly loading mechanism was changed for .NET Core. This was done for a couple of reasons. First, we can set up different assembly load contexts; this lets us load different versions of assemblies into different contexts in the same application. This was not really possible before. Second, we can unload assemblies after we're done with them. Again, this is something that was very difficult to do before.

Manually Loading an Assembly
In getting this to work, my first step was to manually load the assembly by hard-coding the value. Here is the code for that.


Before calling the "GetType" method, this code loads the CSV assembly into the context using "AssemblyLoadContext.Default.LoadFromAssemblyPath". This will load the assembly into the default context (which is the main one that the application uses). The parameter is the assembly file name with the full path.

For the path, there is an assemblyPath variable that is set to the current location of the executable (AppDomain.CurrentDomain.BaseDirectory) with the file name appended (PersonReader.CSV.dll).

This gets us a working application:


But it is of limited usefulness since the CSV reader assembly is hard-coded.

A Different Approach
At this point, I figured that I could try to parse the file name out of the assembly-qualified name that we already have in the configuration file, or I could take a different approach.

When we manually load an assembly into the context like we did above, we also get a reference to that assembly. This means that instead of using "GetType" to locate a type, we can poke into the assembly directly using reflection.

For this approach, I made a few changes to configuration, output folders, and code.

Note: this is not the final version of the code, but you can find it by looking at a particular commit in GitHub: commit/49dc7a33d8071e9eef83d9e1a1d7bba5c3de50cb.

New Configuration
Rather than having the full assembly-qualified name of the type, I created settings for just the parts that I needed. Here is the new configuration (in the App.config file for the commit mentioned above):


Now we have a "ReaderAssembly" key with a value of "PersonReader.CSV.dll" -- the name of the file on the file system. We also have "ReaderType" which is "PersonReader.CSV.CSVReader" -- the fully-qualified name of the reader type.

New Output Folder
In addition, since we will no longer rely on "GetType" being able to find files in the executable folder, I decided to move the reader files to a separate sub-folder in the output. This makes it easier to keep track of the reader assemblies, particularly if we need to remove or change the files.

Along with the new output folder comes updated post-build steps. These are in the PeopleViewer.csproj file for the commit mentioned above. Here is the view from Visual Studio, which is a bit easier to read:


This has 2 copy steps. The first step copies files from the "AdditionalFiles" folder into the output folder. This folder contains the data files that are used by the readers, specifically People.txt (for the CSV reader) and People.db (for the SQL reader).

The next step copies files from the "ReaderAssemblies" folder to a "ReaderAssemblies" subfolder in the output. This contains the dlls for the readers along with the dependencies.

New Code
Along with the new configuration and output location, we have some new code to dynamically load the specified data reader. This is in the ReaderFactory.cs file for the commit mentioned above:


Let's walk through this code.

First we get the "ReaderAssembly" value from configuration. As a reminder, this is "PersonReader.CSV.dll".

Next, we create the full directory path to that file by taking the "BaseDirectory" (where the executable is), appending the new "ReaderAssemblies" subfolder, and then adding the name of the file.

As a side note, the "Path.DirectorySeparatorChar" will pick the correct character for the operating system. So in Windows, it will use the backslash; in Linux and macOS, it will use the forward slash.

Notice that after calling "LoadFromAssemblyPath", we store the return value as "readerAssembly". This is the assembly that we just loaded.

The next step is to get the "ReaderType" from configuration. As a reminder, this is "PersonReader.CSV.CSVReader".

Next we get the reader type out of the loaded assembly. This code uses a little bit of LINQ to reflect into the assembly. "ExportedTypes" is a collection of all of the publicly visible types that are in the assembly. In the query, we go through the types and try to find one that matches the value from configuration. If the type is not found, this method returns null.

The rest of the method is what we had before. Once we have the Type, we can use the Activator to create an instance, and then we cast it to the appropriate type.

Working Code (sort of)
This code seems like a good approach. We can use configuration to decide which assembly and type to load. And when we run the application, it works!


The CSV reader works just fine, but we run into a problem if we try to use one of the other reader types.

Let's update the configuration to use the web service reader. (In the App.config file for the commit mentioned above, comment out the CSV section and uncomment the Service section):


This sets the values for "ReaderAssembly" and "ReaderType" to "PersonReader.Service.dll" and "PersonReader.Service.ServiceReader" respectively.

Unfortunately, this breaks the application. If we run the application and click the button, we get an exception:


This is a "file not found" exception. And the details tell us that it is trying to load the assembly for Newtonsoft.Json version 12.0.0.0. The service reader has a dependency on Newtonsoft.Json.

That brings us to the next problem: loading dependencies.

Assembly Dependencies
In searching for a solution, I came across a tutorial about adding plugin support: Create a .NET Core application with plugins.

This tutorial addresses dependencies. Unfortunately, the described solution does not work for the current code. In the section "Plugin target framework recommendations" we see the following (screenshot and text in case it gets updated):


"Because plugin dependency loading uses the .deps.json file, there is a gotcha related to the plugin's target framework. Specifically, your plugins should target a runtime, such as .NET Core 3.0, instead of a version of .NET Standard. The .deps.json file is generated based on which framework the project targets, and since many .NET Standard-compatible packages ship reference assemblies for building against .NET Standard and implementation assemblies for specific runtimes, the .deps.json may not correctly see implementation assemblies, or it may grab the .NET Standard version of an assembly instead of the .NET Core version you expect." (emphasis mine)
This plugin solution relies on ".deps.json" files to resolve dependencies. And there's our first problem.

.deps.json
The .deps.json file has the dependencies for an assembly. For example, when we build the PeopleViewer application, we get the following output:


In addition to the PeopleViewer.exe (which calls PeopleViewer.dll), we also have PeopleViewer.deps.json. By looking inside this file, we can see the following:


This has a "dependencies" section that shows a dependency on "PersonReader.Interface" version 1.0.0. (This is the interface project that we saw above). Because this is included, that assembly can be loaded along with the PeopleViewer assembly.

But our data reader assemblies do not have .deps.json files:


These assemblies are .NET Standard assemblies. As noted in the plugin tutorial, the dependencies for .NET Standard assemblies cannot be generated without knowing what .NET environment it will be running under. For example, the service reader may need a different version of Newtonsoft.Json when run from .NET Framework compared to running in .NET Core.

Options
To go down the path of the sample plugin architecture, I would need to change the data reader projects to .NET Core from .NET Standard. That is not something that is always practical depending on how the projects are being used.

Additionally, the plugin architecture seemed to be quite a bit more than I needed for this application.

Since all of the reader assemblies and dependencies are in a separate folder, I can take a different path. Instead of trying to figure out how to get the dependencies to load automatically, I can just load them manually.

Manually Loading Assemblies
In the previous code, we manually loaded the one data reader assembly based on configuration. To load the dependencies, we will load all of the assemblies that are in the "ReaderAssemblies" folder.

Here is the code for a "LoadAllAssemblies' method (from the "ReaderFactory.cs" file for the commit mentioned above):


In the first line, we build the path to the "ReaderAssemblies" folder.

Next, "Directory.EnumerateFiles()" will give us an enumeration of all the file names that match our search criteria. In this case, we ask for all files that end with ".dll". Also, we only search the top folder (not any subfolders).

Then we use "foreach" to loop through all the file names and load them into the default context. If there are any files that can't be loaded, then we just skip them.

Assumption
This has the assumption that all of the .dlls in this folder are ones that we want to load. This is a bit easier to do since we have a separate folder. If the reader assemblies were still in the root folder (like we had initially), I would be much more reluctant to try this approach.

Working Code
To get the code working, we call "LoadAllReaderAssemblies" at the top of our factory method (from the "ReaderFactory.cs" file for the commit mentioned above):


And now the application works with the service data reader as well:


Note: If you run this application yourself, you will also need to start the service. To start the service, open a command prompt to the "People.Service" folder and type "dotnet run". For more information on .NET Core services, check out this tutorial: Get Comfortable with .NET Core and the CLI.

Duplicated Code
With the updated solution in place, we have some unnecessary code. Let's take another look at the "GetReader" method (same as above):


In this case, the reader assembly gets loaded twice. When we call "LoadAllReaderAssemblies", everything in that folder is loaded, including the one for the data reader.

Then the next lines are concerned about getting a reference to the "Assembly" object that represents the reader assembly. To do this, we end up loading the data reader assembly a second time.

Rethinking the Solution
Let's go back to the initial problem: Type.GetType() does not automatically load an assembly.

But we saw that it still works when we manually loaded the assembly. Remember this code?


When we manually loaded the "PersonReader.CSV.dll" assembly, GetType worked just fine.

Now that we are loading the reader assembly and all of its dependencies, we can go back to that solution.

Back to Square One
With a better understanding of what's going on, we can go back to where we were initially. We can take our original code and add "LoadAllReaderAssemblies" to the top. Here is that code (in the ReaderFactory.cs file in the final code):


With this, we also need to go back to the original configuration (from the App.config file in the final code):


After all of the assemblies are loaded, GetType returns the Type object that we expect, and the rest of the code works as expected.

Running the application with this configuration gets data from the CSV text file:


And we can change the configuration to use the service:



Wrap Up
So we took a bit of a roundabout way to get back to where we started. But we learned some things along the way.
  • With .NET Core, we need to explicitly load assemblies.
  • With dynamically-loaded .NET Standard assemblies, we need to explicitly load any dependencies.
There are also some things to look into further.
  • Using .NET Core (or other specific framework) projects gives us a .dep.json file that specifies dependencies.
Moving to .NET Core is pretty smooth for the most part. But there are things that pop up that can be frustrating. Eventually we'll have all of those things catalogued, and conversions will be easier.

Happy Coding!

Monday, January 6, 2014

Improving Reflection Performance with Delegates

Reflection is an extremely powerful tool. But one of the drawbacks is performance. In my presentations on Reflection, we look at an application that shows the speed differences between calling methods directly and calling them with Reflection (live presentation on my website; video presentation on Pluralsight).

Now, the point of this application is to show that dynamically invoking a method through reflection is 30 times slower than making a direct method call. This is to encourage us to make sure we only use reflection when we actually need it. But if we do need it, there are ways to improve the performance that I don't talk about in the presentation. Instead of doing dynamic invocation directly, we can use a delegate to improve performance.

The sample code is available here: http://www.jeremybytes.com/Downloads/ReflectionWithDelegates.zip.

Baseline Speed
The sample application shows 4 different ways to call a method (as opposed to the 2 methods shown in the original presentation sample).

Here's the code for the direct method call:


Most of this code is boiler-plate to get the metrics for the UI -- and I use the word "metrics" here very loosely. This code performs the loop 10,000,000 times (which is a lot). That's how many times we need to do this so that we can get some human-noticeable times. And this just gives us a general idea. When we run this code, the computer is doing other stuff (background operations, UI updates, network polling, etc.), so the exact numbers will vary.

This important bits of the above method are the first line (where we create a new List object) and the line inside the "for" loop (where we add the indexer to the list).

When running on my machine (a dual-core i7), we get the following result:


Dynamic Invocation with Reflection
When we try the same functionality using reflection, we get a much different result. Here's the code:


This code is a little bit different. We still create the new List variable. But then, we use reflection to execute the "Add" method. To do this, we get a Type object based on List<int>. Then we call "GetMethod" which gives us back an MethodInfo object.

Then inside the loop, we call Invoke on the MethodInfo object. The parameters look a bit strange for this method. The first parameter is the instance we can to call the method on -- in this case, it is the "list" variable that we created at the top. The second parameter is an object array for the method parameters. Since we need to pass in a single parameter (an integer), we create an object array with a single value.

The result of this method call is the same as the method in the first example -- we add 10,000,000 items to a List object.

The performance is significantly different:


Instead of 127 milliseconds, we get 3.5 seconds! That's around 30 times longer.

Using an Interface
The recommendation in the Practical Reflection presentation is to use Reflection to load and instantiate an object (to give us the flexibility of run-time loading), but then cast the object to a known interface in order to call the method. This gives us the best of both worlds.

Here's that code:


At the top of this method, we get a Type object based on List<int>, and then we use the Activator class to create an instance of that type. Notice that our variable ("list") is an interface type (IList<int>) rather than a concrete type.

Because of this, even though we create the object dynamically, we can call the "Add" method just like we would on a normal object. (And we see this inside the "for" loop.)

The result is that we do not get a performance hit. It runs at the same speed as a direct method call:


Reflection with a Delegate
But there is another option as well. If we absolutely need to use Reflection to dynamically call a method multiple times, we can use a delegate to improve the performance.

Here's the code for that:


This code is a bit more complicated. Notice at the very top (outside of our button click handler), we have a definition of a delegate ("ListAddDelegate"). Notice the signature for this delegate. The first parameter is "List<int>" -- this is the instance of the list that the "Add" method is called on. The second parameter is the parameter for the "Add" method -- in this case, an integer. The delegate returns void because List<T>.Add (the method we want to call) returns void.

The first 3 lines of the button click handler match the reflection method. We create an instance of a List<int>, get a Type variable, and then use GetMethod to get a MethodInfo object.

But then we create a delegate instance. We use "Delegate.CreateDelegate" to create a delegate object based on our MethodInfo object. The first parameter is the Type of the delegate we want (our custom ListAddDelegate), and the second parameter is the MethodInfo object (the "addMethod" that we got above). Then we cast this whole thing to a ListAddDelegate.

Inside our "for" loop, we simply invoke our custom delegate by calling "addDelegate" with the 2 parameters (the List<int> instance and the integer that we want to "Add").

The result is much better performance:


This time is inline with the direct method call and the interface method call.

Here are all of the results together:


Wrap Up
The point of the original speed comparison is to show that using Reflection to call a method is 30 times slower than making a direct call. But if we do find that we need reflection to dynamically call a method multiple times, we do have the option of creating a delegate to handle the method calls.

There are several ways to come up with similar answers. As developers, we should be used to this. What we need to do is weigh the pros and cons of each approach in the context of our own application -- keeping in mind that we want to balance flexibility and performance.

Happy Coding!

Wednesday, December 18, 2013

I Can Write That Method with 1 Line of Code

I've worked with developers who prided themselves with terseness -- writing a function with the absolutely fewest lines of code possible.

Are fewer lines actually better?
I'll stick with my standard answer: "It depends." Unfortunately, many times, fewer lines of code comes at the cost of readability and maintainability -- and as you know, these are very big concerns of mine.

Let's take a look at some code to see if we can find a good balance. The code is taken from "IEnumerable, ISaveable, IDontGetIt: Interfaces in .NET". Now, I have a tendency to be a bit verbose when I'm starting an application. This helps with debugging, especially in the early stages.

Here's how the method stands in the downloaded code (from the RepositoryFactory.cs file). We'll refer to this as the 5-line version:


This method dynamically loads a type from an assembly based on configuration. In this case, it returns a concrete repository class that implements the IPersonRepository interface. Let's do a quick step through this method to see what each line does:
  1. We pull a value out of the configuration file. This value is the assembly-qualified name of the type we want to load.
  2. Based on the assembly-qualified name (from #1), we use the GetType method to generate a CLR Type object.
  3. With a Type object, we can use the Activator class to create an actual instance of that Type. The CreateInstance method returns an "object" that we stick into the "repoInstance" variable.
  4. Since we need something that implements the IPersonRepository interface, we cast our "object" to "IPersonRepository".
  5. Finally, we return the repository, which is an IPersonRepository.
That's a lot of steps, and it's okay if you don't understand exactly what's going on. What we want to focus on is the number of intermediate variables that we have in this method. We actually have 4 variables: repoTypeName, repoType, repoInstance, and repo.

Getting Rid of Intermediate Variables
We can eliminate some of these intermediate variables by inlining them -- basically, we just replace the variable usage with its assignment.

The easiest thing to do is to combine lines 3 and 4 to get rid of the intermediate "repoInstance" variable:


So, we immediately cast the return value from the CreateInstance method to an "IPersonRepository".

But then, do we really need the "repo" variable? All we do is return it in the next line. So, let's combine lines 3 and 4:


This gives us a fairly compact method.

But Why Stop There?
Let's keep going. We can get rid of the "repoTypeName" variable by combining lines 1 and 2:


We just take the "AppSettings" statement and use it directly as a parameter for the "GetType" method. Now we're down to 2 lines of code, and we only have 1 intermediate variable.

Can we get this down to just 1 line?


Of course we can. But it's a little difficult to read since it's stretched out. Let's add a few line breaks:


This is still a single line of code (at least in the source -- what it gets compiled to is a different issue). Now, it's easier to see everything. And the terseness-obsessed developer would be proud.

Is This a Good Idea?
Now that we've whittled things down to 1 line of code, we need to stop and ask ourselves if this is a good idea. Let's look at this code from a couple different perspectives.

Readability
The number one problem I have with the terse code is readability. The degree of readability will depend on the experience of our developers. If our developers have not worked much with reflection, then this code is nearly indecipherable. The advantage with the intermediate variables is that we get some clues as to what is going on in each step based on the variable names (even if we disregard the variable types themselves).

"repoTypeName" lets us know that the value coming out of configuration is the name of a Type. "repoType" lets us know that this is a CLR Type object. "repoInstance" lets us know that we now have an instance of a particular type. And so on...

Maintainability
What if something goes wrong with this code? Let's set a breakpoint:


Uh oh. It doesn't look like this breakpoint is going to do us much good. At least with the intermediate variables, we'll be able to set good breakpoints, and we can see the values that are produced during each step.

Performance
The 1 line version has a small performance benefit. We can see this by looking at the IL that is generated.

Here's the IL for the 1-line version:


And here's the IL for the 5-line version:


If we look at the "meat", we see that the same instructions are run in each version:
  1. call to "get_AppSettings". This gets the AppSettings property from the ConfigurationManager.
  2. ldstr for "RepositoryType". This is the string literal for the setting we want to load.
  3. callvirt to "get_Item(string)". This gets the value that we're looking for from configuration.
  4. call to "GetType". This is the Type.GetType call from our code.
  5. call to "CreateInstance". This is the Activator.CreateInstance call from our code.
  6. isinst for "IPersonRepository". This is the cast to the IPersonRepository interface.
  7. ret. This returns the final value.
The difference between the IL output is in that the 5-line version has the intermediate variables. These are the "stloc" and "ldloc" calls that are interspersed in the code.

So, technically, the 1-line version will be a little faster because it does not deal with these intermediate variables.

But (and this is a BIG BUT), the slowest parts of this method are the reflection calls ("GetType" and "CreateInstance"). These are orders of magnitude slower that the variable code. So, in this case, we really should not worry about the differences in performance. Any slight gain we might get will be overshadowed by the slowness of the reflection code.

Finding the Balance
So, how do we find the balance?

Here's my approach: I start out with the verbose (5-line) method. During the development and early testing process, I want to make sure that the intermediate values are what I expect. So, I like having the extra variables (to put in the Watch window) and extra lines (to add breakpoints).

But once I'm past this, I'd like to refactor things down a bit. My balance point for this method is the 3-line version:


I like this for a couple of reasons. First, I can easily verify (with a breakpoint or watch) that the value I pull out of configuration is what I expect it to be. This is the brittlest part of this method -- configuration is just text in an XML file, and this is very easy to typo or just enter wrong values.

Next, I can verify that the dynamically-loaded assembly is actually available. The "GetType" method uses reflection to load up the specified assembly and get the type information out. If the assembly is not available (or the assembly is available but does not contain the expected type), this step will fail. Again, this is easy to breakpoint, and if there is an exception, it will point right to this line of code.

I'm good with combining the rest of the method into a single line. In my opinion, this is still readable. And we can easily break this into separate lines if we do happen to have problems in this section.

Know Your Team
A big part of this is to understand the skill levels of the people you work with (or the people who will be working with the code). Whenever I'm given a choice between dumbing-down code and making developers better, I will always choose to make the developers better. But sometimes we do not have control over that.

We need to code at a level that the developers understand. So, if I am in an environment where reflection is an unfamiliar topic, then I might just stick with the original 5-line version.

So, know yourself, know your team, and strike the balance that's appropriate for your environment.

Happy Coding!

Friday, November 22, 2013

New Pluralsight Course: Practical Reflection in .NET

My latest course is now available on Pluralsight: Practical Reflection in .NET.

Practical Reflection in .NET
Reflection is an extremely powerful feature of .NET. But there is a big difference between what we can do and what we should do. We'll take a quick look at what reflection is capable of and then narrow our focus to practical uses: balancing flexibility, safety, and performance.
When I first saw reflection, I thought it was really cool (and a bit scary, too). And at the time, I didn't think that I would ever use it in my own applications. It really looked like a tool for people who build developer tools or have an extremely specialized situation. But I was wrong.

Reflection is useful for everyday developers, too. Now, it's true that I only use a very small subset of features that reflection offers, but that's probably a good thing. Reflection has some drawbacks -- primarily speed and safety, so we probably want to limit how much we use anyway.

Real World Reflection
I showed a bit of reflection in the C# Interfaces course. In the Dynamic Loading section, we loaded an assembly based on configuration information, and then created an instance of a type. This is reflection. I skipped over the details of reflection since we were focusing on interfaces.

In the latest course, we take a closer look at the reflection parts of this sample so that we can really understand what's going on. This includes looking at the assembly-qualified names that we used in the configuration file, as well as the Type class that loads a type based on that, and the Activator class that we used to create an instance of that type.

Adding Flexibility
Another example shows how we can dynamically load assemblies from the file system and pick out the types that we want. Why would we ever want to do this? Well, the scenario is a rules engine that allows our clients to create their own custom business rules by simply building classes that implement a particular interface. Then they can drop the resulting assemblies into a particular folder that the application picks up automatically. This allows for greater flexibility of our application.

And this technique can be expanded beyond this scenario as well.

Uh-Oh
The last section of the course covers that scarier parts of reflection. It turns out that nothing in our assemblies is secret. If someone has the assembly, he can look at all of our variables, strings, classes, and functions -- even if they are marked "private". But we shouldn't panic about this, we just need to keep it in mind when we're writing our applications.

There are several techniques that we can use to make sure that our confidential information stays confidential. If you want to learn more about these techniques, be sure to watch the course ;-)

Wrap Up
Pluralsight offers tons of great training courses, and they've been expanding immensely over the last year. Pluralsight offers a 10-day free trial that gives you 200 minutes to use however you like. I've also got some free trials that offer 1 week of unlimited usage. So, drop me an email if you're interested.

This is my 5th published course, and I'm working hard on my next one. I've had a lot of fun producing these, and I love to hear from people who have found them valuable (and also from people who have some suggestions -- I'm always trying to make things better).

Happy Coding!