One of the areas of Silverlight that not much has been written about yet is the factoring of applications. Applications don’t have to be packaged in one big XAP file; Silverlight presents numerous options for dividing code and resources among multiple assemblies, packaging resources inside or outside the XAP file in one file or multiple files, and loading assemblies and resources at load time or on demand. And for one feature in particular, much of what has been written is wrong. It’s time to set the record straight and learn something really cool at the same time.
Suppose you’re writing a Silverlight application that uses classes in a library assembly named HelperLib.dll. You add a reference to HelperLib.dll to your project and HelperLib.dll gets embedded in the application package (the XAP file) and loaded into the AppDomain automatically. You can use types in HelperLib.dll as if they were types in your code. No problem there; it just works.
But now let’s take it one step further. Suppose you have lots of library assemblies and don’t want to swell the size of the XAP file with them because depending on how your users interact with the application, you may not need all those assemblies. To first order, the solution is simple. You deploy the library assemblies alongside the XAP file in your server’s ClientBin folder and use WebClient to download them on demand. To load an assembly after it’s been downloaded, you do this:
But what comes next isn’t very pretty. Because you didn’t add a reference to the assembly to your project, you can’t use the new operator to instantiate the assembly’s types. (Why? Because without the assembly reference, the compiler doesn’t know about the types.) So you resort to reflection—specifically, to Assembly.CreateInstance:
This creates an instance of the type named Foo. But now if you want to go deeper and access some of Foo’s properties or methods, you have to perform even more reflection. This gets unwieldy fast, and as a practical matter, it means that you’ll probably give up and go back to embedding all those library assemblies in your XAP file.
Fortunately, there is a better way. The Silverlight docs on MSDN suggest that you can download an assembly, load it into the AppDomain, and still have the benefit of strong typing. The docs even offer a working example. Many people have complained that the example doesn’t work, but it does work if you run it exactly as is and don’t change a thing. The example is extremely fragile, but the docs don’t tell you why. To make matters worse, the example can fail given the right circumstances. As my friend and colleague Jeffrey Richter commented, the fact that it works at all is really more a matter of luck than anything else.
Here’s a recipe for downloading assemblies on demand and using the types in those assemblies as if they were right there in the application assembly:
Step 3 was a mouthful, and it probably makes no sense at all, so let’s look at an example. Assuming Foo is a type in a dynamically loaded library assembly, you want to be able to do this:
But even if you follow steps 1 and 2 above, you’ll be met with this:
The solution is to restructure the code this way:
Now it works as intended, and inside the UseFoo method, you can use a Foo as a Foo and not worry about any more casting or reflecting.
Now the big question: why does new Foo() work when it’s in a separate, specially attributed method, but not when the method that loads the assembly calls it? This is a terrific example of a situation in which knowledge of CLR internals can make you a better Silverlight programmer. Before the JIT compiler compiles a method, it checks all the types in that method and ensures that the assemblies implementing those types have been loaded. When you do a new Foo() inside the method that loads the assembly, the CLR tries to load the assembly before you get a chance to call AssemblyPart.Load. But when you break it out into a separate method, the method gets to execute and gets to load the assembly. What does the MethodImpl attribute do? It ensures that the compiler won’t get in the way by inlining the code in the CreateFoo method. That’s why the sample in the Silverlight docs “gets lucky.” It lacks the attribute, and without the attribute, there’s no guarantee that new Foo() won’t get inlined.
So yes, it is possible to load assemblies on demand and still enjoy the benefit of strong typing. But you need to know what you’re doing. And now, hopefully, you do. 🙂
Addendum: In the “big” .NET Framework, you can resolve assembly loading issues using the AppDomain.AssemblyResolve event. That event exists in Silverlight, but unfortunately, it’s attributed SecurityCritical, which means user code can’t register handlers for it.