演练:嵌入托管程序集中的类型(C# 和 Visual Basic)
如果嵌入强命名托管程序集中的类型信息,则可以对应用程序中的类型进行松耦合,以实现版本中立性。 即,可以将程序编写为使用多个托管库版本中的类型,而不必对每个版本重新编译一次程序。
类型嵌入经常与 COM 互操作一起使用,例如使用 Microsoft Office 中的自动化对象的应用程序。 嵌入类型信息后,程序的同一个生成可以处理不同计算机上的不同 Microsoft Office 版本。 但是,您也可以在完全托管的解决方案中使用类型嵌入。
程序集中可以嵌入的类型信息具有以下特征:
程序集至少公开一个公共接口。
嵌入的接口使用 ComImport 特性和 Guid 特性(及一个唯一 GUID)进行批注。
程序集使用 ImportedFromTypeLib 特性或 PrimaryInteropAssembly 特性和程序集级别的 Guid 特性进行批注。 (默认情况下,Visual Basic 和 Visual C# 项目模板包含程序集级别的 Guid 特性。)
指定可以嵌入的公共接口后,可以创建实现这些接口的运行时类。 然后,客户端程序可以在设计时引用包含这些公共接口的程序集,并将该引用的 Embed Interop Types 属性设置为 True,以此嵌入这些接口的类型信息。 这等效于使用命令行编译器,通过 /link 编译器选项引用该程序集。 之后,客户端程序可以加载运行时对象类型化为这些接口的实例。 如果创建新版本的强命名运行时程序集,则不必使用更新的运行时程序集来重新编译客户端程序。 实际上,客户端程序将使用公共接口的嵌入类型信息,从而继续使用任何可用的运行时程序集版本。
由于类型嵌入的主要功能是提供对来自 COM 互操作程序集的类型信息的嵌入的支持,因此以下限制在将类型信息嵌入完全托管解决方案中时适用:
仅嵌入特定于 COM 互操作的特性;忽略其他特性。
如果一个类型使用泛型参数且泛型参数的类型是嵌入类型,则不能跨程序集边界使用该类型。 跨程序集边界的示例包括从另一个程序集调用方法或从另一个程序集中定义的类型派生类型。
不嵌入常量。
System.Collections.Generic.Dictionary<TKey, TValue> 类不支持将嵌入类型作为密钥。 可以实现自己的字典类型以支持将嵌入类型作为密钥。
在本演练中,您将执行以下操作:
创建一个这样的强命名程序集:它具有包含可嵌入类型信息的公共接口。
创建一个这样的强命名运行时程序集:它实现该公共接口。
创建一个这样的客户端程序:它嵌入该公共接口中的类型信息,并创建该运行时程序集中的类的实例。
修改并重新生成运行时程序集。
运行客户端程序,查看正在使用的运行时程序集新版本,而不必重新编译该客户端程序。
备注
对于在以下说明中使用的某些 Visual Studio 用户界面元素,您的计算机可能会显示不同的名称或位置。这些元素取决于您所使用的 Visual Studio 版本和您所使用的设置。有关更多信息,请参见 Visual Studio 设置。
创建接口
创建类型等效性接口项目
在 Visual Studio 中的**“文件”菜单上,指向“新建”,然后单击“项目”**。
在**“新建项目”对话框的“项目类型”窗格中,确保选中“Windows”。 在“模板”窗格中,选择“类库”。 在“名称”框中,键入 TypeEquivalenceInterface,然后单击“确定”**。 至此新项目创建完毕。
在**“解决方案资源管理器”中,右击 Class1.vb 或 Class1.cs 文件,然后单击“重命名”**。 将文件重命名为 ISampleInterface.vb 或 ISampleInterface.cs,然后按 Enter。 重命名该文件后,会相应地将类重命名为 ISampleInterface。 此类将表示类的公共接口。
右击 TypeEquivalenceInterface 项目,然后单击**“属性”。 在 Visual Basic 中单击“编译”选项卡,或者在 Visual C# 中单击“生成”**选项卡。 将输出路径设置为开发计算机上的一个有效位置,如 C:\TypeEquivalenceSample。 本演练后面的步骤中也将用到此位置。
在仍处于项目属性编辑阶段时,单击**“签名”选项卡。 选择“为程序集签名”选项。 在“选择强名称密钥文件”列表中,单击“<新建...>”。 在“密钥文件名称”框中,键入 key.snk。 清除“使用密码保护密钥文件”复选框。 单击“确定”**。
打开 ISampleInterface.vb 或 ISampleInterface.cs 文件。 将以下代码添加到 ISampleInterface 类文件,以创建 ISampleInterface 接口。
Imports System.Runtime.InteropServices <ComImport()> <Guid("8DA56996-A151-4136-B474-32784559F6DF")> Public Interface ISampleInterface Sub GetUserInput() ReadOnly Property UserInput As String ... End Interface
using System; using System.Runtime.InteropServices; namespace TypeEquivalenceInterface { [ComImport] [Guid("8DA56996-A151-4136-B474-32784559F6DF")] public interface ISampleInterface { void GetUserInput(); string UserInput { get; } ... } }
在**“工具”菜单上,单击“创建 Guid”。 在“创建 GUID”对话框中,单击“注册表格式”,再单击“复制”。 单击“退出”**。
在 Guid 特性中,删除示例 GUID,粘贴从**“创建 GUID”**对话框中复制的 GUID。 删除所复制 GUID 中的大括号 ({})。
在 Visual Basic 中的**“项目”菜单上,单击“显示所有文件”**。 如果您使用的是 Visual C#,请跳过此步骤。
如果您使用的是 Visual Basic,请在**“解决方案资源管理器”中,展开“我的项目”文件夹。 如果您使用的是 Visual C#,则展开“属性”**文件夹。 双击 AssemblyInfo.vb 或 AssemblyInfo.cs 文件。 将以下特性添加到文件中。
<Assembly: ImportedFromTypeLib("")>
[assembly: ImportedFromTypeLib("")]
保存该文件。
保存项目。
右击 TypeEquivalenceInterface 项目,然后单击**“生成”**。 此时将编译类库 .dll 文件,并保存到指定的生成输出路径中,如 C:\TypeEquivalenceSample。
创建运行时类
创建类型等效性运行时项目
在 Visual Studio 中的**“文件”菜单上,指向“新建”,然后单击“项目”**。
在**“新建项目”对话框的“项目类型”窗格中,确保选中“Windows”。 在“模板”窗格中,选择“类库”。 在“名称”框中,键入 TypeEquivalenceRuntime,然后单击“确定”**。 至此新项目创建完毕。
在**“解决方案资源管理器”中,右击 Class1.vb 或 Class1.cs 文件,然后单击“重命名”**。 将文件重命名为 SampleClass.vb 或 SampleClass.cs,然后按 Enter。 重命名该文件后,会相应地将类重命名为 SampleClass。 此类将实现 ISampleInterface 接口。
右击 TypeEquivalenceRuntime 项目,然后单击**“属性”。 在 Visual Basic 中单击“编译”选项卡,或者在 Visual C# 中单击“生成”**选项卡。 将输出路径设置为 TypeEquivalenceInterface 项目中使用的那个位置,如 C:\TypeEquivalenceSample。
在仍处于项目属性编辑阶段时,单击**“签名”选项卡。 选择“为程序集签名”选项。 在“选择强名称密钥文件”列表中,单击“<新建...>”。 在“密钥文件名称”框中,键入 key.snk。 清除“使用密码保护密钥文件”复选框。 单击“确定”**。
右击 TypeEquivalenceRuntime 项目,然后单击**“添加引用”。 单击“浏览”选项卡,浏览到输出路径文件夹。 选择 TypeEquivalenceInterface.dll 文件,单击“确定”**。
在 Visual Basic 中的**“项目”菜单上,单击“显示所有文件”**。 如果您使用的是 Visual C#,请跳过此步骤。
在**“解决方案资源管理器”中,展开“引用”文件夹。 选择 TypeEquivalenceInterface 引用。 在 TypeEquivalenceInterface 引用的“属性”窗口中,将“特定版本”属性设置为“False”**。
将以下代码添加到 SampleClass 类文件,以创建 SampleClass 类。
Imports TypeEquivalenceInterface Public Class SampleClass Implements ISampleInterface Private p_UserInput As String Public ReadOnly Property UserInput() As String Implements ISampleInterface.UserInput Get Return p_UserInput End Get End Property Public Sub GetUserInput() Implements ISampleInterface.GetUserInput Console.WriteLine("Please enter a value:") p_UserInput = Console.ReadLine() End Sub ... End Class
using System; using System.Collections.Generic; using System.Linq; using System.Text; using TypeEquivalenceInterface; namespace TypeEquivalenceRuntime { public class SampleClass : ISampleInterface { private string p_UserInput; public string UserInput { get { return p_UserInput; } } public void GetUserInput() { Console.WriteLine("Please enter a value:"); p_UserInput = Console.ReadLine(); } ... } }
保存项目。
右击 TypeEquivalenceRuntime 项目,然后单击**“生成”**。 此时将编译类库 .dll 文件,并保存到指定的生成输出路径中,如 C:\TypeEquivalenceSample。
创建客户端项目
创建类型等效性客户端项目
在 Visual Studio 中的**“文件”菜单上,指向“新建”,然后单击“项目”**。
在**“新建项目”对话框的“项目类型”窗格中,确保选中“Windows”。 在“模板”窗格中,选择“控制台应用程序”。 在“名称”框中,键入 TypeEquivalenceClient,然后单击“确定”**。 至此新项目创建完毕。
右击 TypeEquivalenceClient 项目,然后单击**“属性”。 在 Visual Basic 中单击“编译”选项卡,或者在 Visual C# 中单击“生成”**选项卡。 将输出路径设置为 TypeEquivalenceInterface 项目中使用的那个位置,如 C:\TypeEquivalenceSample。
右击 TypeEquivalenceClient 项目,然后单击**“添加引用”。 单击“浏览”选项卡,浏览到输出路径文件夹。 选择 TypeEquivalenceInterface.dll 文件(不是 TypeEquivalenceRuntime.dll),单击“确定”**。
在 Visual Basic 中的**“项目”菜单上,单击“显示所有文件”**。 如果您使用的是 Visual C#,请跳过此步骤。
在**“解决方案资源管理器”中,展开“引用”文件夹。 选择 TypeEquivalenceInterface 引用。 在 TypeEquivalenceInterface 引用的“属性”窗口中,将“嵌入互操作类型”属性设置为“True”**。
将以下代码添加到 Module1.vb 或 Program.cs 文件,以创建客户端程序。
Imports TypeEquivalenceInterface Imports System.Reflection Module Module1 Sub Main() Dim sampleAssembly = Assembly.Load("TypeEquivalenceRuntime") Dim sampleClass As ISampleInterface = CType( _ sampleAssembly.CreateInstance("TypeEquivalenceRuntime.SampleClass"), ISampleInterface) sampleClass.GetUserInput() Console.WriteLine(sampleClass.UserInput) Console.WriteLine(sampleAssembly.GetName().Version) Console.ReadLine() End Sub End Module
using System; using System.Collections.Generic; using System.Linq; using System.Text; using TypeEquivalenceInterface; using System.Reflection; namespace TypeEquivalenceClient { class Program { static void Main(string[] args) { Assembly sampleAssembly = Assembly.Load("TypeEquivalenceRuntime"); ISampleInterface sampleClass = (ISampleInterface)sampleAssembly.CreateInstance("TypeEquivalenceRuntime.SampleClass"); sampleClass.GetUserInput(); Console.WriteLine(sampleClass.UserInput); Console.WriteLine(sampleAssembly.GetName().Version.ToString()); Console.ReadLine(); } } }
按 Ctrl+F5,生成并运行程序。
修改接口
修改接口
在 Visual Studio 中的**“文件”菜单上,指向“打开”,然后单击“项目/解决方案”**。
在**“打开项目”对话框中,右击 TypeEquivalenceInterface 项目,然后单击“属性”。 单击“应用程序”选项卡。 单击“程序集信息”按钮。 将“程序集版本”和“文件版本”**的值都更改为 2.0.0.0。
打开 ISampleInterface.vb 或 ISampleInterface.cs 文件。 将下面一行代码添加到 ISampleInterface 接口。
Function GetDate() As Date
DateTime GetDate();
保存该文件。
保存项目。
右击 TypeEquivalenceInterface 项目,然后单击**“生成”**。 此时将编译类库 .dll 文件的新版本,并保存到指定的生成输出路径中,如 C:\TypeEquivalenceSample。
修改运行时类
修改运行时类
在 Visual Studio 中的**“文件”菜单上,指向“打开”,然后单击“项目/解决方案”**。
在**“打开项目”对话框中,右击 TypeEquivalenceRuntime 项目,然后单击“属性”。 单击“应用程序”选项卡。 单击“程序集信息”按钮。 将“程序集版本”和“文件版本”**的值都更改为 2.0.0.0。
打开 SampleClass.vb 或 SampleClass.cs 文件。 将下面数行代码添加到 SampleClass 类。
Public Function GetDate() As DateTime Implements ISampleInterface.GetDate Return Now End Function
public DateTime GetDate() { return DateTime.Now; }
保存该文件。
保存项目。
右击 TypeEquivalenceRuntime 项目,然后单击**“生成”**。 此时将编译类库 .dll 文件的更新版本,并保存到指定的生成输出路径中,如 C:\TypeEquivalenceSample。
在文件资源管理器中,打开输出路径文件夹(例如,C:\TypeEquivalenceSample)。 双击 TypeEquivalenceClient.exe,运行该程序。 程序将反映 TypeEquivalenceRuntime 程序集的新版本,而未经重新编译。