可插入和可绘图对象






4.44/5 (10投票s)
如何使用反射来加载具有预定义行为的未知组件。
引言
如今,可插拔控件的概念似乎并不新鲜,但在2002年,它确实很新颖。当时,我的一个学生(Denis Soroka)在学习了一些.NET Framework的基础知识后(愿上帝保佑Anders Hejlsberg和他创造奇迹的惊人技巧),很快就意识到了使用插件的优势。顺便说一句,我并没有因为摆线而发疯(有些人可能认为我疯了)。这是另一篇文章(纯属巧合)。
背景
想象一辆卡车运送一堆可插拔的DLL,并将它们卸载到应用程序指定的文件夹中。假设文件夹名为*Plugins*。然后,想象一个FileSystemWatcher
对象监控此文件夹内的活动,并向您的应用程序通知所有更改。最后,想象一个算法可以区分正确的(或所需的)DLL与其他(外部的)DLL,并启动所需的功能,即在运行时创建具有预定义行为的未知对象并启动其方法和属性表单。
Denis Soroka很快就实现了这个场景(基于我之前使用开发的Graph
类创建多个控件的任务)。在这里,我将讨论这个应用程序(现在用于C#课程之一),并尝试解释在运行时启动未知对象并使其工作(完成您希望它们完成的工作)的技术。
Using the Code
启动程序。尝试单击右侧列表中的行。然后,在不停止程序的情况下,将所有(或任何)DLL(您会在*Plugins*文件夹中找到)移动到文件系统中的其他位置。观察程序的行为。将DLL放回*Plugins*文件夹。现在,您知道我想讨论什么了。
解决方案结构
让*将要插入的模块*的预定义(规定)行为在IPlottable
接口中描述。它用于定义游戏规则:使用Graph
类显示不同的图表。它于1987年作为C++练习编写。现在,它作为简单的C#类复活了。
public interface IPlottable
{
PointF[] GetPoints();
void ShowProperty();
void DestroyProperty();
string Name { get; }
string Caption { get; }
string Unit { get; }
event Action ChangeProperty;
}
这些需求似乎足以呈现单曲线图表。此代码应放置在类型为类库的单独项目中,以便我们解决方案中的任何其他项目都可以获取对IPlottable
接口的引用,从而遵守游戏规则。
public class Aperiodic : IPlottable
{
// Data, Properties & Methods specific
// to the Aperiodic transient curve (plot)
// IPlottable Implementation
}
观察*Plugins*文件夹并与新成员交互的逻辑应在主项目中实现。让它驻留在以下类中
public partial class FormMain : Form
{
// Data
string pluginPath, pluginName;
FileSystemWatcher watcher;
Dictionary<string, Type> plots;
IPlottable plot;
// Methods
public FormMain()
{
pluginPath = FindFolder("Plugins");
plots = new Dictionary<string, Type>();
InitializeComponent();
}
// Some Other Methods
}
让我们看看一个方法的代码,该方法识别FileSystemWatcher
监控的文件夹中的新DLL(插件)。这里,plot
是当前活动的插件。listDll
是一个ListBox
,用于显示在文件夹中找到的所有插件的名称,plots
是一个集合,用于记住*将要插入的对象*。后者存储<string, Type>
类型的对。
void FillDllList()
{
if (plot != null)
plot.DestroyProperty();
pluginName = null;
listDll.BeginUpdate();
listDll.Items.Clear();
plots.Clear();
foreach (string file in Directory.GetFiles(pluginPath))
{
if (Path.GetExtension(file) != ".dll")
continue;
Assembly a = Assembly.LoadFrom(file);
foreach (Type type in a.GetTypes())
{
if (type.GetInterface("IPlottable") != null)
{
plots.Add(type.Name, type);
listDll.Items.Add(type.Name);
}
}
}
ResetPlot();
listDll.EndUpdate();
}
需要注意的事项是
Assembly.LoadFrom(file)
a.GetTypes()
type.GetInterface("IPlottable")
它们是如何使用反射(著名的.NET Framework工具来调查未知现实)的示例。
请注意,您可以使用另一个(或更确切地说是附加的)指标来区分*正确的*DLL与其他DLL。除了测试IPlottable
接口的存在之外,您还可以测试DLL提供商可能已将其设置为插件的一个(或多个)自定义属性。
这可以在(使用)文件*AssemblyInfo.cs*中完成。在任何新的.NET项目中都有这样的文件,但可能从未使用过。对于那些知道此功能并经常使用它的人,我表示歉意。我经常删除*AssemblyInfo.cs*文件,因为它从未被使用。这是一个测试自定义属性的示例。此片段可以插入到FillDllList
方法中。
object[] attr = a.GetCustomAttributes(true);
if (attr.Length < 13)
continue;
string descr = null;
foreach (object o in attr)
{
AssemblyDescriptionAttribute da = o as AssemblyDescriptionAttribute;
if (da != null)
{
descr = da.Description;
break;
}
}
if (descr == null || descr != "PlotPlugin") // Some foreign assembly
continue;
说到FileSystemWatcher
对象,没有什么好说的。逻辑很简单。
void SetWatcher()
{
watcher = new FileSystemWatcher
{
Path = pluginPath,
EnableRaisingEvents = true,
SynchronizingObject = this
};
watcher.Changed += OnWatcherChanged;
watcher.Created += OnWatcherChanged;
watcher.Deleted += OnWatcherChanged;
watcher.Renamed += OnWatcherChanged;
}
我们将FileSystemWatcher
生成的所有事件都简化为唯一的方法OnWatcherChanged
。这里重要的设置是:watcher.SynchronizingObject = this
。这将有助于您调试项目,以便您不必使用Invoke/BeginInvoke方案。
我相信您会在这里庞大的CodeProject库中找到并阅读更多关于COM对象相关怪癖和麻烦(隐式调用的线程等)的信息。
关注点
我希望您注意启动未知对象的方式。这是通过以下代码片段完成的
Type type = plots[pluginName];
if (plot != null)
plot.DestroyProperty();
plot = type.GetConstructor(Type.EmptyTypes).Invoke(null) as IPlottable;
plotControl.SetGraph(plot);
plot.ShowProperty();
plot.ChangeProperty += delegate() { plotControl.SetGraph(plot); };
同样,技巧在于使用反射的代码
plot = type.GetConstructor(Type.EmptyTypes).Invoke(null) as IPlottable;
祝您编程愉快!