创建强类型可交换类库
创建一个强类型系统,用于插入可互换的类库(DLL),而无需拥有多个版本的应用程序主程序。
问题
我们决定使用一个现有的内部应用程序并将其对外发布。该应用程序使用了一个专有的类库,该类库不能包含在内。但是,当该应用程序对外发布时,它需要一个新的类库来提供相同的功能。此外,现有的内部类库计划在不久的将来进行重构。
挑战在于维护应用程序的单一代码库,以同时支持现有和新的 DLL,并具有足够的灵活性来支持尚待设计的现有 DLL 重写。这似乎是使用 System.Reflection
命名空间和 GetInterface()
方法的绝佳场景。这将允许我为每个实现选项创建可交换的类库,并以类型安全的方式访问它们。
解决方案
计划是创建一个接口来描述将从外部类库公开的属性、方法和事件。任何外部动态链接库都必须实现此接口。然后,我们将创建一个实现相同接口的辅助类库。辅助类库将包含加载实现接口的外部 DLL 所需的所有逻辑。主应用程序将仅链接到辅助类库,而无需了解链接特定外部类库所需的逻辑。
我们将使用 VB.NET 2005 创建一个包含多个相当通用的项目的解决方案。这些项目除了演示此概念外,没有其他用途。我们需要做的第一件事是创建一个描述我们可交换库的接口。我们将创建一个名为 InterfaceDLL 的类库项目。该项目包含一个名为 ILibrary
的单一接口。
Public Interface ILibrary
Function GetName() As String
End Interface
此接口规定,我们的应用程序打算链接的任何 DLL 都必须公开一个返回 String
数据类型的 GetName()
函数。重要的是要注意,我们为接口创建了一个单独的项目。这是因为接口的完全限定名称包含项目命名空间。当其他项目引用此接口时,它们将引用 <InterfaceDLL.>ILibrary
。如果我们只是跨项目链接接口,它仍然属于各个项目,因此,就不是同一个接口。
接下来,我们将创建第一个使用此接口的类库。这将是 RedDLL 项目。它将包含以下代码。
Imports InterfaceDLL
Public Class clsMe
Implements ILibrary
Public Function GetName() As String Implements ILibrary.GetName
Return "RedDLL"
End Function
End Class
此代码导入 InterfaceDLL
命名空间,并指示此类实现 ILibrary
接口。我们只需要实现 GetName()
函数。在这里,我们返回库名,“RedDLL”。我们将创建一个几乎相同的 BlueDLL 项目,使用相同的接口。在我们的测试案例中,代码相同,只是返回“BlueDLL”有所不同。它包含在示例解决方案中。
接下来,我们将创建辅助类库项目,在本例中为 ColorDLL。该项目包含此示例的所有 .NET 魔力。首先,与前两个类库一样,它导入 InterfaceDLL
,并公开 GetName()
函数。但是,此函数与其他函数略有不同。它利用了我们已导入的 System.Reflection
命名空间。我们可以使用该命名空间中的 LoadFrom()
方法从给定的类库(在本例中指定在 app.config 中)加载程序集。然后,我们将遍历程序集,查找实现 ILibrary
接口的对象。找到后,我们将调用其 GetName()
函数。
Imports InterfaceDLL
Imports System.Reflection
Public Class clsMe
Implements ILibrary
' Grab the name from main application's app.config
Private strColorDLL As String = _
System.Configuration.ConfigurationSettings.AppSettings("ColorDLL")
Public Function GetName() As String Implements ILibrary.GetName
Dim objAssembly As Reflection.Assembly
Dim clsColor As ILibrary = Nothing
' We're assuming the library is in the bin directory for this application
Dim strColorDLLPath As String = _
System.IO.Path.Combine(My.Application.Info.DirectoryPath, strColorDLL)
Dim objTypes() As Type
Dim objFound As Type
Dim strRV As String = String.Empty
' Load the assembly from the DLL
objAssembly = Reflection.Assembly.LoadFrom(strColorDLLPath)
' March through the types in the assembly
objTypes = objAssembly.GetTypes
For Each objItem As Type In objTypes
' Looking for our Interface
objFound = objItem.GetInterface("ILibrary")
If objFound IsNot Nothing Then
' We wouldn't be here if it wasn't the right type, so we can DirectCast
clsColor = DirectCast(objAssembly.CreateInstance(objItem.FullName), _
ILibrary)
Exit For
End If
Next
' If we've got it, call our function
If clsColor IsNot Nothing Then
strRV = clsColor.GetName
End If
' Let 'em have it
GetName = strRV
End Function
End Class
对于包含导入 ILibrary
接口的类的每个项目,您都需要添加对 InterfaceDLL 项目的项目引用。这是通过在 My Project 中打开 References 选项卡,然后单击 Add 按钮来完成的。在 Add Reference 表单中打开 Projects 选项卡,然后找到 InterfaceDLL 项目。
最后,我们将创建主应用程序项目,在本例中,它被创造性地命名为 MainExe。在这个例子中,我们只是将代码放在 Form1
的 Load
事件中。
Public Class Form1
Dim clsColor As New ColorDLL.clsMe
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Me.lblLibrary.Text = clsColor.GetName
End Sub
End Class
Form1_Load()
简单地从辅助类调用 GetName()
函数。MainExe 对 RedDLL 或 BlueDLL 的内部工作一无所知,也不关心。只要 RedDLL 和 BlueDLL 实现 ILibrary
接口,辅助库就会默默地调用它们。
现在,这里有一些在后台发生的事情。首先,当开发人员构建 MainExe 项目时,必须向相应的 RedDLL 或 BlueDLL 项目或 DLL 添加引用,以及向辅助类添加引用。此外,我们将在 app.config 中添加一行来指定哪个 DLL 将由辅助类库加载。请记住,app.config 键是在辅助类库 ColorDLL 中引用的。它不为主应用程序引用。
<appSettings>
<!--It is assumed that the library is in the bin directory,
we only need its name-->
<add key="ColorDLL" value="BlueDLL.dll"/>
</appSettings>
结论
现在,使用此方法,您可以创建一个用于插入可互换库的系统,而无需拥有多个版本的应用程序主程序。您也已准备好应对实现相同接口的未来库。最后,您不必偏离您的类型安全开发。