Well this may be old news to many of you really smart guys, but I like this application design so much, that I just had to blog about it.
So the choice is generally between ultimate flexibility (100% reflection), or better performance (add reference, screw reflection).
Here is a good tradeoff between both. It lets you use Reflection, without making you pay the associated performance penalty.
a) Define a base class. For instance, I am writing anapplication that will move content between various blog providers. So at the very heart of it, I have a BlogMoverEngine base class as follows -
public abstract class BlogMoverEngine
{
public virtual string EngineName
{
get { return this.GetType().ToString(); }
}
public abstract bool IsConfigured { get; }
public abstract Form ConfigurationForm { get; }
public abstract BlogMLBlog ExtractBlogContent();
}
b) Great, now go ahead and actually implement an Engine based on the above base class. For instance, one blog engine could be CommunityServer, so you'd create a class called CommunityServerEngine that inherits from the above BlogMoverEngine class.
c) Awesome. Now don't add reference to the app that uses your CommunityServerEngine. Instead, throw the DLL in a directory called Engines. This can be made automatic via a Custom Build Step that looks like this - copy $(TargetDir)\*.dll $(SolutionDir)\BlogMoverUI\Engines
d) Very nice, now write a LoadEngines method in the client app that has access to the "Engines" directory. Here's the method -
sourceEngines = new BO.Engines();
string[] fileNames = Directory.GetFiles(@"C:\PlayArea\BlogMover\BlogMoverUI\Engines", "*.dll");
foreach (string fileName in fileNames)
{
Assembly engineAssembly = Assembly.LoadFile(fileName);
Type[] allTypes = engineAssembly.GetExportedTypes();
foreach (Type classType in allTypes)
{
if (classType.IsSubclassOf(typeof(BlogMoverEngine)))
{
BlogMoverEngine engineInstance = engineAssembly.CreateInstance(classType.ToString()) as BlogMoverEngine;
if (engineInstance != null)
{
sourceEngines.Add(engineInstance);
} }
}
}
Note: BO.Engines is simply of type List<BlogMoverEngine> type
e) Great! Now, what the above code let you do is, it let you create an instance of the appropriate classes in the dlls in the Engines directory. I can now go ahead and databind this to a combobox (just say for example), as shown below:
sourceComboBox.DataSource = sourceEngines;
sourceComboBox.DisplayMember = "EngineName";
f) And then to use the above instances that were created using reflection, in a strongly typed manner, do the below -
void sourceComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
ComboBox cmbSender = sender as ComboBox;
BlogMoverEngine blogMoverEngine = cmbSender.SelectedItem as BlogMoverEngine;
if (blogMoverEngine != null)
{
// Use the engine in a strongly typed manner
}
}
Awesome! You can now instantiate and use classes using reflection, but not pay the price for reflection when you actually use them. Instantiation is literally a single method (constructor call), so the overall cost isn't that high. Damn Cool! To use the above in non-full-trust environments, you can isolate the reflection factory in a seperate assembly and give it elevated rights, so your app in general can run under minimal trust, and still use the above.