在.NET中开发组件的方法

在.NET中开发组件的方法

本文对“组件”这个术语进行定义并提供特别是与组件编程相关的 .NET 框架编程概念的概述。虽然“组件”这个术语有多种含义,但在 .NET 框架中,组件是指实现 System.ComponentModel.IComponent 接口的一个类,或从实现该接口的类中直接或间接导出的类。

如果想要您的组件或控件在其他编程语言中可以使用,您必须以符合公共语言规范 (CLS) 的语言创作它们并确保所有公共和保护的成员都符合 CLS。.NET 框架 SDK 为四种符合 CLS 的语言(Visual Basic .NET、C#、C++ 的托管扩展和 JScript .NET)提供编译器。

类、组件和控件

本主题对组件和控件进行了定义;此处进行的讨论可以帮助您决定何时实现一个是组件或控件的类。

以下列表为实施者提供了全面的指南。

  • 如果类使用外部资源但不用于设计图面,则实现 System.IDisposable,或者从直接或间接实现 IDisposable 的类导出。
  • 如果类用于设计图面(例如 Windows 窗体或 Web 窗体设计器),则实现 System.ComponentModel.IComponent,或者从直接或间接实现 IComponent 的类导出。请注意,IComponent 扩展了 IDisposable,因此 IComponent 类型始终是 IDisposable 类型。与不是 IComponentIDisposable 类型相比,IComponent 类型的性能系统开销要校但 IDisposable 类型的这一不足通常可由在设计时和运行时安置 IComponent 的能力来弥补。(在本主题的后面部分将对该安置功能进行说明)。
  • 如果需要由引用封送的可设计的(用于设计图面)类,则可从 System.ComponentModel.Component 导出。Component 是由引用封送的 IComponent 类型的基实现。
  • 如果需要由值封送的可设计类,则可从 System.ComponentModel.MarshalByValueComponent 导出。MarshalByValueComponent 是由值封送的 IComponent 类型的基实现。
  • 如果需要在对象模型层次结构中引入 IComponent 类型(由于是单继承,因此不能从像 ComponentMarshalByValueComponent 这样的基实现导出),则实现 IComponent
  • 如果需要提供用户界面的可设计类,则该类是控件。控件必须从基控件类(System.Windows.Forms.Control 或 System.Web.UI.Control)之一直接或间接导出。

注意 如果类既不是可设计类又没有外部资源,则不需要 IComponentIDisposable 类型。

下面是组件、控件、容器和站点的定义。

组件

.NET 框架中,组件是指实现 System.ComponentModel.IComponent 接口的一个类,或从实现 IComponent 的类中直接或间接导出的类。在编程中,“组件”这个术语通常用于可重复使用并且可以和其他对象进行交互的对象。.NET 框架组件满足这些一般要求,另外还提供诸如控制外部资源和设计时支持等功能。

控制外部资源

IComponent 接口扩展了 System.IDisposable 接口,在后者的协定中有一个名为 Dispose 的方法。在其 Dispose 方法实现中,组件必须显式释放外部资源。与垃圾回收过程中发生的默认、不确定的清理相比,这提供了一个确定的方法来释放资源。开发人员必须在整个包容层次结构中传播 Dispose 以确保组件的子级也释放资源。另外,导出的组件必须调用其基类的 Dispose 方法。

注意 即使通过 Dispose 提供对资源的显式控制,您也应该始终通过终结器(析构函数)提供隐式清理以防止资源的永久泄漏(如果某个用户未能在您的组件上调用 Dispose 可能会出现这种情况)。

下面的示例显示了在基组件和在导出的组件中实现 Dispose 的方式。

[C#]
public class BaseComponent : IComponent {
 // IComponent extends IDisposable.
 public void Dispose() {
 Dispose(true);
 GC.SuppressFinalize(this); 
}
 protected virtual void Dispose(bool disposing) {
 if (disposing) {
 //Free other state (managed objects).
 }
 //Free your own state (unmanaged objects).
 }
 //Simply call Dispose(false).
 ~BaseComponent(){
 Dispose (false);
 }
}
 
//Derived component.
public class DerivedComponent : BaseComponent {
 
protected override void Dispose(bool disposing) {
 if (disposing) {
 // Free other state.
 }
 // You must invoke the Dispose method of the base class.
 base.Dispose(disposing);
 //Free your own state.
 ...
 }
 //No finalizer/destructor.
 //No Dispose() method.
}

设计时支持

.NET 框架中,组件的一个重要功能就是它们是可设计的,这意味着作为组件的类可用于快速应用程序开发 (RAD) 环境(如 Visual Studio .NET)中。可将组件添加到 Visual Studio .NET 的工具箱中,也可将组件拖放到某个窗体上,还可以在设计图面上对组件进行操作。请注意,对 IComponent 类型的基本设计时支持已经内置于 .NET 框架中;组件开发人员无须进行额外的工作就可利用基本设计时功能。

承载组件

组件可以被放置(承载)在一个容器(本主题稍后部分进行了定义)中。当组件被放置后,它通过其站点(本主题稍后部分进行了定义)与容器进行交互并能够通过站点从其容器查询并得到服务。为了确保容器拆卸后资源被释放,容器必须实现 IDisposable 接口。在其 Dispose 方法的实现中,容器必须释放它保留的所有资源,并调用它包含的每个组件的 Dispose 方法。

包容是逻辑上的,不需要可视的表示形式。放置数据库组件的中间层容器就是非可视包容的一个示例。可在 Visual Studio .NET 的 Windows 窗体设计器和 Web 窗体设计器中找到可视包容。可视化设计图面是承载窗体组件(Web 窗体中的页组件)的容器。

封送组件

组件可以是可远程控制的或不可远程控制的。可远程控制的组件由引用或值进行封送。封送涉及跨边界(如应用程序域(轻量进程)、进程、甚至是计算机)发送对象。当对象由引用封送时,会创建一个代理对该对象进行远程调用。当对象由值进行封送时,跨相关边界发送该对象的一个序列化的副本。

封装系统资源的可远程控制组件、较大的可远程控制组件或作为单个实例存在的可远程控制组件应该由引用封送。由引用封送的组件的基类是 System.ComponentModel.Component。该基类实现 IComponent 并从 MarshalByRefObject 导出。.NET 框架类库中的许多组件从 Component 导出,包括 System.Windows.Forms.Control(Windows 窗体控件的基类)、System.Web.Services.WebService(使用 ASP.NET 创建的 XML Web services 的基类)和 System.Timers.Timer(生成周期性事件的类)。

仅保留状态的可远程控制组件应该由值封送。由值封送的组件的基类是 System.ComponentModel.MarshalByValueComponent。该基类实现 IComponent 并从 Object 导出。.NET 框架类库中只有极少组件是从 MarshalByValueComponent 导出的。所有这些组件都在 System.Data 命名空间中(DataColumnDataSetDataTableDataViewDataViewManager)。

注意 由值封送的对象和由引用封送的对象其基类分别是 ObjectMarshalByRefObject,但相应的派生类命名为 MarshalByValueComponentComponent。命名方案背后的逻辑是越常用的类型,其名称就越简单。

如果不对组件进行远程控制,则不要从 Component 的基实现导出,而应直接实现 IComponent

控件

控件是提供(或实现)用户界面 (UI) 功能的组件。.NET 框架为控件提供两个基类:一个用于客户端 Windows 窗体控件,另一个用于 ASP.NET 服务器控件。它们是 System.Windows.Forms.Control 和 System.Web.UI.Control。.NET 框架类库中的所有控件都是直接或间接从这两个类导出的。System.Windows.Forms.Control Component 导出,它本身提供 UI 功能。System.Web.UI.Control 实现 IComponent,它还提供结构,在这个结构上可以很方便地添加 UI 功能。

注意 每个控件都是一个组件,但并不是每个组件都是控件。

容器和站点

如果您正在为 Windows 窗体或 Web 窗体页(ASP.NET 页)开发组件和控件,则不需要实现容器或站点。Windows 窗体和 Web 窗体的设计器就是 Windows 窗体和 ASP.NET 服务器控件的容器。容器向放置在其中的组件和控件提供服务。在设计时,控件放置在设计器中并从设计器获得服务。为了保持完整性,以下给出了容器和站点的定义。

容器

容器是一个实现 System.ComponentModel.IContainer 接口的类,或从实现该接口的类导出的类。容器在逻辑上包含一个或多个组件,这些组件叫做容器的子组件。

站点

站点是一个实现 System.ComponentModel.ISite 接口的类,或从实现该接口的类导出的类。站点由容器提供,用来管理其子组件及与子组件进行通讯。通常,容器和站点作为一个单元来实现。

属性概述

组件应该定义属性而不是公共字段,因为可视化设计器(例如 Visual Studio .NET)在属性浏览器中显示属性,而不显示字段。(在本主题的最后列出了定义属性的其他强制性的原因)。

属性就像智能字段。属性通常具有带访问函数的专用数据成员,在语法上属性被作为类的字段进行访问。(虽然属性可以具有不同的访问级别,但此处的讨论将重点放在公共访问这种更加常见的情况上。)因为 Visual Basic 的许多版本中都可以使用属性,Visual Basic 程序员可以跳过本主题。

属性定义通常由以下两部分组成。

专用数据成员的定义。

[C#]

private int number = 0;

使用属性声明语法对公共属性进行的定义。该语法通过 getset 访问函数将专用数据成员和公共属性关联起来。

[C#]

public int MyNumber

{

// Retrieves the number data member.

get

{

return number;

}

// Assigns to the number data member.

set

{

number = value;

}

}

value 这个术语是属性定义语法中的一个关键字。在呼叫代码中,将变量 value 分配给属性。value 的类型必须同它被分配到的属性的声明类型相同。

虽然属性定义中通常包含专用数据成员,但这不是必需的。get 访问器可以不访问私有数据成员就返回一个值。其中一个示例就是某个属性的 get 方法返回系统时间。属性启用数据隐藏,访问器方法隐藏属性的实现。

在不同的编程语言中,属性语法存在一些差异。例如,术语“属性”不是 C# 中的关键字,但却是 Visual Basic .NET 中的关键字。有关语言特定的信息,请参阅有关该语言的文档。

下面的示例在类 SimpleProperty 中定义了一个名为 MyNumber 的属性,并从类 UsesSimpleProperty 访问 MyNumber。

[C#]

public class SimpleProperty

{

private int number = 0;

public int MyNumber

{

// Retrieves the data member number.

get

{

return number;

}

// Assigns to the data member number.

set

{

number = value;

}

}

//Other members.

}

public class UsesSimpleProperty

{

public static void Main()

{

SimpleProperty example = new SimpleProperty();

// Sets the property.

example.MyNumber = 5;

// Gets the property.

int anumber = example.MyNumber;

}

}

通常来说,getset 方法和其他方法没有什么不同。它们可以执行任何程序逻辑、引发异常、被重写以及用编程语言允许的任意修饰符进行声明。但是请注意,属性也可以是静态的。如果属性是静态的,则在 getset 方法可以实现的功能上有一些局限性。有关详细信息,请参阅编程语言参考。

属性的类型可以是基元类型、基元类型的集合、用户定义类型或用户定义类型的集合。对于所有基元类型,.NET 框架提供实现字符串到值转换的类型转换器。当属性可以使用类型转换器时,它显示在设计器的属性浏览器中。如果您定义自定义属性并想让属性浏览器显示它们,则必须实现自定义类型转换器。

当属性的数据类型是枚举时,开发环境(例如 Microsoft Visual Studio .NET)在“属性”窗口中将该属性显示为下拉列表。如果属性的数据类型是具有属性的类,这些属性叫做定义属性的子属性。在 Visual Studio .NET 的“属性”窗口中,用户可以展开属性以显示其子属性。

向属性添加特性十分重要,这样它们可以在设计时正确地显示在属性浏览器中。

您应该公开组件的属性而不是公共字段,因为属性可以被指定版本,它们允许数据隐藏,并且访问器方法可以执行附加逻辑。通常,由于实时优化,属性不比字段更耗费资源。

从组件引发事件

从组件引发事件和从不是组件的类中引发事件没有什么不同。由于组件是可以设计的,因此您必须将特性应用到事件成员以确保它们在属性浏览器中正确显示。

组件的设计时特性

因为组件可以显示在设计器(例如 Visual Studio .NET)中,所以它们需要向设计时工具提供元数据的特性。本节说明并提供一组常用的设计时特性。

特性和设计器支持

因为设计时特性向可视设计工具提供有价值的信息,所以它们对在设计时正确显示您的控件及其成员非常重要。

在下面的代码片段中,CategoryAttribute 特性启用属性浏览器以在“Alignment”类别中显示 TextAlignment 属性。DescriptionAttribute 特性允许属性浏览器在用户单击某个属性时显示该属性的简要说明。

[C#]
[
Category("Alignment"),
Description("Specifies the alignment of text.")
]
public ContentAlignment TextAlignment { //... }

C# 和 Visual Basic .NET 用户请注意: C# 和 Visual Basic .NET 中,名为 AttributeNameAttribute 的特性类可以完全像特性语法中的 AttributeName 一样进行引用。

某些设计时特性是在类级别应用的。DesignerAttribute 特性在类级别应用,它通知窗体设计器使用哪个设计器类来显示控件。组件与默认的设计器 (System.ComponentModel.Design.ComponentDesigner) 相关联,Windows 窗体和 ASP.NET 服务器控件与它们自己的默认设计器相关联。只有在您为组件或控件定义自定义设计器时才应用 DesignerAttribute

[C#]
//Associates the designer class SimpleControl.Design.SimpleDesigner
//with Simple.
[ Designer(typeof(SimpleControl.Design.SimpleDesigner))]
 public class Simple : WebControl { //... }

属性和事件的公共特性

下表列出了常用于属性和事件的特性。除非另外说明,属性和事件的特性在代码中紧接在属性或事件声明的前面。如下面的示例所示。

[C#]
// To apply CategoryAttribute to the BorderColor 
// property, place it immediately before the declaration
// of the BorderColor property.
[Category("Appearance")] 
public Color BorderColor;
// To apply DescriptionAttribute to the Click event, 
// place it immediately before the declaration
// of the Click event.
[Description("The Click event of the button")]
public event EventHandler Click;
<TD style="BORDER-RIGHT: #eae8e3; PADDING-RIGHT: 0.75pt; BORDER-TOP: #eae8e3; PADDING-LEFT: 0.75pt; PADDING-BOTTOM: 0.75pt; BORDER

属性

应用于

说明

BrowsableAttribute

属性和事件

指定属性或事件是否应该显示在属性浏览器中。

CategoryAttribute

属性和事件

指定类别的名称,在该类别中将对属性或事件进行分组。当使用了类别时,组件属性和事件可以按逻辑分组显示在属性浏览器中。

DescriptionAttribute

属性和事件

定义一小块文本,该文本将在用户选择属性或事件时显示在属性浏览器底部。

BindableAttribute