In object-oriented languages, the bulk of the work should be performed within objects. All but the simplest applications require constructing one or more custom classes, each with multiple properties and methods used to perform tasks related to that object. This lesson discusses how to create custom classes.
在面向对象的语言中,大部分的工作都是用对象来实现的。就连最简单的应用需求都会创建一个或更多的自定义类,而每个自定义类又包含了大量的属性和方法用来执行与对象有关的任务。本课就是讨论如何建立自定义类。
|
After this lesson, you will be able to: 课后你将可以
· Describe and use inheritance.描述和使用继承
· Describe and use interfaces.描述和使用接口
· Describe and use partial classes.描述和使用飞类(见Microsoft .NET Framework 2.0 Application Development Foundation 翻译系列2(第一章:框架基本原理)中Overview概述)
· Create a generic type, and use the built-in generic types.建立一个泛型类型和使用net框架中自带(默认)的泛型类型。
· Respond to and raise events.响应和触发事件
· Add attributes to describe assemblies and methods.添加用来描述程序集和方法的属性
· Move a type from one class library to another using type forwarding.利用类型传递(见Microsoft .NET Framework 2.0 Application Development Foundation 翻译系列2(第一章:框架基本原理)中Overview概述),将一个类型从一个类库移动到另一个类库中。
Estimated lesson time: 40 minutes 课时40分钟
|
|
The .NET Framework has thousands of classes, and each class has many different methods and properties. Keeping track of all these classes and members would be impossible if the .NET Framework were not implemented extremely consistently. For example, every class has a ToString method that performs exactly the same task—converting an instance of the class into a string. Similarly, many classes support the same operators, such as comparing two instances of a class for equality.
在net框架中有数以千计的类,并且每个类都有许多不同的方法和属性。如果net框架无法保持始终如一,那么想要了解所有这些类和成员几乎是不可能的。举个例子来说,每个类都有一个ToString()方法用来正确的执行相同的任务-将一个类的实例转换为一个字符串。同样的,许多类都支持相同的操作,例如象比较两个类实例是否相等的操作。
This consistency is possible because of inheritance and interfaces (described in the next section). Use inheritance to create new classes from existing ones. For example, you will learn in Chapter 6, "Graphics," that the Bitmap class inherits from the Image class and extends it by adding functionality. Therefore, you can use an instance of the Bitmap class in the same ways that you would use an instance of the Image class. However, the Bitmap class provides additional methods that enable you to do more with pictures.
由于继承和接口的使用才使这种一致性成为可能(将在下一部分描述)。使用继承从一个已经存在的类来建立一个新的类。举例来说,你将会在第六章绘图中学到的Bitmap类就是从Image类继承并添加功能扩展而得来的。因此,你可以象使用Image类的实例一样,使用同样的方法来操作Bitmap类的实例。只不过,Bitmap类提供了额外的操作图片的方法。
You can easily create a custom exception class by inheriting from System.ApplicationException, as shown here:
你可以容易的通过继承System.ApplicationException来建立一个自定义异常类。如下代码。
' VB
Class DerivedException
Inherits System.ApplicationException
Public Overrides ReadOnly Property Message() As String
Get
Return "An error occurred in the application."
End Get
End Property
End Class
// C#
class DerivedException : System.ApplicationException
{
public override string Message
{
get { return "An error occurred in the application."; }
}
}
You can throw and catch the new exception because the custom class inherits that behavior of its base class, as shown here:
你可以抛出并捕获这个新的异常,因为这个自定义异常类继承了它基类的行为。
' VB
Try
Throw New DerivedException
Catch ex As DerivedException
Console.WriteLine("Source: {0}, Error: {1}", ex.Source, ex.Message)
End Try
// C#
try
{
throw new DerivedException();
}
catch (DerivedException ex)
{
Console.WriteLine("Source: {0}, Error: {1}", ex.Source, ex.Message);
}
Notice that the custom exception not only supports the throw/catch behavior, but it also includes a Source member (as well as others) inherited from System.ApplicationException.
注意,这个自定义异常类不仅仅支持throw和catch行为,而且包含一个从System.ApplicationException继承来的Source成员(其它自定义异常类也都有)
Another benefit of inheritance is the ability to use derived classes interchangeably. For example, there are five classes that inherit from the System.Drawing.Brush base class: HatchBrush, LinearGradientBrush, PathGradientBrush, SolidBrush, and TextureBrush. The Graphics.DrawRectangle method requires a Brush object as one of its parameters; however, you will never pass the base Brush class to Graphics.DrawRectangle. Instead, you will pass one of the derived classes. Because they are each derived from the Brush class, the Graphics.DrawRectangle method can accept any of them. Similarly, if you were to create a custom class derived from the Brush class, you could also pass that class to Graphics.DrawRectangle.
继承的另一个好处是它有能力可以代替它的父类(来源类)。举例来说,有五个类,它们继承于System.Drawing.Brush基类:HatchBrush, LinearGradientBrush, PathGradientBrush, SolidBrush, and TextureBrush。Graphics.DrawRectangle方法需要一个Brush对象作为参数,可是,你不能传递这个Brush基类给Graphics.DrawRectangle方法。那么,你可以传递继承了这个Brush基类的类中的一个。因为它们都来源于Brush类,这个Graphics.DrawRectangle方法可以接收它们中的任何一个。同样的,如果你建立了一个来源于Brush类的自定义类,你也可以将它们传递给Graphics.DrawRectangle方法。
Interfaces, also known as contracts, define a common set of members that all classes that implement the interface must provide. For example, the IComparable interface defines the CompareTo method, which enables two instances of a class to be compared for equality. All classes that implement the IComparable interface, whether custom-created or built in the .NET Framework, can be compared for equality.
接口,也可以理解为“约束”,它定义一个公共成员集合,实现这个接口的所有类都要提供这个公共成员的集合。举个例子,IComparable接口定义了CompareTo方法,用来允许比较同一个类的两个实例是否相等。所有实现了IComparable接口的类,无论它是自定义的还是net框架自带的,都可以对它们进行比较。
IDisposable is an interface that provides a single method, Dispose, to enable assemblies that create an instance of your class to free up any resources the instance has consumed. To create a class that implements the IDisposable interface using Visual Studio 2005, follow these steps:
IDisposable提供了一个独有的方法Dispose,这个方法用来允许你建立的类实例的程序集去释放任何被实例占用的资源。下面示范使用VS2005建立一个实现了IDisposable接口的类,步骤如下:
1. Create the class declaration. For example:建立这个类的声明
2. ' VB
3. Class BigClass
4. End Class
5.
6. // C#
7. class BigClass
8. {
9. }
10.Add the interface declaration. For example:添加接口的声明
11. ' VB
12. Class BigClass
13. Implements IDisposable
14. End Class
15.
16. // C#
17. class BigClass : IDisposable
18. {
19. }
20.If you are using Visual Basic, Visual Studio should automatically generate method declarations for each of the required methods. If it does not, delete the Implements command and try again; Visual Studio may still be starting up. If you are using C#, right-click the Interface declaration, click Implement Interface, and then click Implement Interface again, as shown in Figure 1-1.
如果你是使用VB,VS将自动建立实现接口需要的每一个方法声明。如果VS没有自动建立,请删除实现接口的命令并重试。VS有可能还在初始化。如果你用的是C#,右键点击这个接口的声明,点击“实现接口”菜单项,然后,再点击“实现接口”子项,如图1-1
图 1-1: Visual Studio simplifies implementing an interface
21.Write code for each of the interface's methods. In this example, you would write code in the Dispose method to deallocate any resources you had allocated.
为接口的每个方法编写代码。在这个例子中,你需要在Dispost方法中编写代码,用来释放你曾经分配过的任何资源。
Table 1-6 lists the most commonly used interfaces in the .NET Framework.
表1-6列出了大部分在net框架中经常使用的接口
Class |
Description |
IComparable |
Implemented by types whose values can be ordered; for example, the numeric and string classes. IComparable is required for sorting. 通过类型来实现哪些值能够被排序;例如,数字和string类。IComparable是用来存储数据所必须的。 |
IDisposable |
Defines methods for manually disposing of an object. This interface is important for large objects that consume resources, or objects that lock access to resources such as databases. 定义手动释放一个对象的方法。这个接口对于很多消耗资源的对象或者象数据库这样的独占资源的对象都非常重要。 |
IConvertible |
Enables a class to be converted to a base type such as Boolean, Byte, Double, or String. 允许将一个类转换为一个基本类型,例如boolean,byte,double,或者string。 |
ICloneable |
Supports copying an object.为对象的复制提供支持 |
IEquatable |
Allows you to compare to instances of a class for equality. For example, if you implement this interface, you could say "if (a = = b)"。 比较类实例是否相等。例如,假设你实现了这个接口,你可以这样做"if (a = = b)"。 |
IFormattable |
Enables you to convert the value of an object into a specially formatted string. This provides greater flexibility than the base ToString method. 允许你将一个对象的值转换为一个指定格式的字符串。与ToString()方法相比,它提供了更大的灵活性。 |
You can create your own interfaces, too. This is useful if you need to create multiple custom classes that behave similarly and can be used interchangeably. For example, the following code defines an interface containing three members:
你也可以建立你自己的接口。当你要建立多个具有能够使用的、相似功能的自定义类时,这对于你是非常有用的。例如,下面代码定义一个接口,这个接口包含了三个成员。
' VB
Interface IMessage
' Send the message. Returns True is success, False otherwise.
Function Send() As Boolean
' The message to send.
Property Message() As String
' The Address to send to.
Property Address() As String
End Interface
// C#
interface IMessage
{
// Send the message. Returns True is success, False otherwise.
bool Send();
// The message to send.
string Message { get; set; }
// The Address to send to.
string Address { get; set; }
}
If you implement that interface in a new class, Visual Studio generates the following template for the interface members:
如果你在一个新的类中实现这个接口,VS将自动生成这个接口成员的模板代码块。
' VB
Class EmailMessage
Implements IMessage
Public Property Address() As String Implements IMessage.Address
Get
End Get
Set(ByVal value As String)
End Set
End Property
Public Property Message() As String Implements IMessage.Message
Get
End Get
Set(ByVal value As String)
End Set
End Property
Public Function Send() As Boolean Implements IMessage.Send
End Function
End Class
// C#
class EmailMessage : IMessage
{
public bool Send()
{
throw new Exception("The method or operation is not implemented.");
}
public string Message
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public string Address
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
}
If you create a custom class and later decide that it would be useful to have multiple classes with the same members, Visual Studio has a shortcut to extract an interface from a custom class. Simply follow these steps:
如果你建立一个自定义类,并随后决定让它具有与多个类相同的成员,VS有一个快捷的方法,用来从一个自定义类中提取出一个接口。只需下面这些简单的步骤。
1. Right-click the class in Visual Studio 2005.在VS2005中右键点击这个类。
2. Click Refactor and then click Extract Interface.点击“Refactor”然后再点击“Extract Interface”
3. Specify the interface name, select the public members that should form the interface, and then click OK.指定这个接口的名称,选择公共成员后点击ok。
Classes can implement multiple interfaces. Therefore, a class could implement both the IComparable and IDisposable interfaces.
类能够实现多个接口。因此,一个类能够将IComparable和IDisposable两个接口都实现。
|
.NET 2.0 |
Partial classes are new in .NET 2.0. |
只在net2.0中提供。
Partial classes allow you to split a class definition across multiple source files. The benefit of this approach is that it hides details of the class definition so that derived classes can focus on more significant portions.
飞类允许你将一个类的定义进行分割,并将各个部分放到多个源文件中。这种方法的好处是,它将类定义的详细定义隐藏起来,因此可以使你关注更重要的部分。
The Windows Form class is an example of a built-in partial class. In Visual Studio 2003 and earlier, forms classes included code generated by the form designer. Now that code is hidden in a partial class named form.Designer.vb or form.Designer.cs.
Window Form 类就是一个net自带的飞类的例子。在vs2003或更早的版本中,forms类通过form设计器生成代码。现在,代码则隐藏在form.Designer.vb或form.Designer.cs飞类中。
In Visual Basic, you must select Show All Files in the Solution Explorer to see the partial class files. In C#, that view is enabled by default. Partial classes aren't part of the exam objectives, but you need to know about them so that you can find the Form Designer code when you create a new Windows Form.
在VB中,你必须在解决方案管理器中选择显示所有文件功能后,才能看到飞类文件。而在C#中,这是默认显示的。飞类不属于考试内容,但是你需要知道它们。这样你才能在建立一个新的Window Form时找到这个Form设计器代码。
Generics are part of the .NET Framework's type system that allows you to define a type while leaving some details unspecified. Instead of specifying the types of parameters or member classes, you can allow code that uses your type to specify it. This allows consumer code to tailor your type to its own specific needs.
泛型是.net框架类型系统的一部分,它允许你去定义一个不具体指定的类型。然后,在代码中,再用那些指定了类型的参数或成员的类,替换你定义的这个没有具体指定的类型。这允许实例代码将这个类型转换为它需要的类型。
|
Exam Tip |
Generic types are new in .NET 2.0, and you will probably see an unusually large number of questions about generics on the exam. 泛型在net2.0中是新的类型,在这个考试中,你也许会遇到大量的、不常见的关于泛型的问题。 |
The .NET Framework version 2.0 includes several generic classes in the System.Collections.Generic namespace, including Dictionary, Queue, SortedDictionary, and SortedList. These classes work similarly to their nongeneric counterparts in System.Collections, but they offer improved performance and type safety.
Net框架2.0版本中,在System.Collections.Generic命名空间包含了几个泛型类,包括Dictionary,Queue,SortedDictionary,和SortedList。这些类和在System.Collections命名空间中的那些与它们同名的类具有同样的功能。但是,它们提供了性能改善和类型安全。
|
More Info—Generic collections |
The .NET Framework version 2.0 includes the System.Collections.Generic namespace, which provides built-in collections that offer improved performance over standard collections. For more information, refer to Chapter 4, "Collections and Generics." System.Collections.Generic命名空间中的Collections(连接集)比标准Collections提供了更好的性能。查看更多信息,参考第四章“Collections(连接集) 和 泛型” |
Versions 1.0 and 1.1 of the .NET Framework did not support generics. Instead, developers used the Object class for parameters and members and would cast other classes to and from the Object class. Generics offer two significant advantages over using the Object class:
在net框架1.0和1.1中是不支持泛型的。 代替方案是,开发人员使用Object类型的参数和成员,并将它们赋给其它类型,或者获取Object类型。泛型提供了比Object类更好的两点重要的优势。
· Reduced run-time errors The compiler cannot detect type errors when you cast to and from the Object class. For example, if you cast a string to an Object class and then attempt to cast that Object to an integer, the compiler will not catch the error. Instead, the runtime will throw an exception. Using generics allows the compiler to catch this type of bug before your program runs. Additionally, you can specify constraints to limit the classes used in a generic, enabling the compiler to detect an incompatible type.
· 简化运行时错误 当你传递或者获取Object类时,编译器是无法识别类型错误的。举个例子,如果你将一个字符串赋给一个Object类,然后,尝试将这个Object类赋给一个整数,编译器不会捕获到错误。但在运行时将会抛出一个异常。而使用泛型,编译器就能在程序运行前捕获这个错误类型。更好的是,在一个泛型中,你可以约束类型匹配,也就是说,这种约束允许编译器能够识别不匹配的类型。
· Improved performance Casting requires boxing and unboxing (explained later in Lesson 4, "Converting Between Types"), which steals processor time and slows performance. Using generics doesn't require casting or boxing, which improves run-time performance.
· 性能优化 造型(参考下面“造型”(Casting)的定义)会产生装箱和拆箱(稍后会在第四课“类型间的转换”中解释),这将会损耗处理器时间并且减慢性能。而使用泛型,就不需要造型或装箱过程,这改善了运行时的性能。
“造型”(Casting)的定义:
基本类型转换:比如说一个方法或者变量接受一个较小的类型作为参数,而实际传入一个较大的,在传入的时候,就必须做 “类型转换”
如:
double d =3.1415d;
int i = (int)3.1415d;
think in java中称之为“窄化转换” (narrowing conversion)
以后还有遇到“类类型转换”也可以说 “造型”(Casting)
·
Real World真实世界
Tony Northrup
I haven't been able to reproduce the performance benefits of generics; however, according to Microsoft, generics are faster than using casting. In practice, casting proved to be several times faster than using a generic. However, you probably won't notice performance differences in your applications. (My tests over 100,000 iterations took only a few seconds.) So you should still use generics because they are type-safe.
我不想再复述泛型对性能的好处;无论如何,按照微软的说法,泛型比造型要快。在实际中,虽然造型会有几次比泛型要快,可是,在你的应用程序中,你可能不会注意到性能上有什么不同。(我反复测试了超过100000次,这仅用了很少的时间。)所以,你应该坚持使用泛型,因为它们是类型安全的。
|
|
First, examine the following classes. Classes Obj and Gen perform exactly the same tasks, but Obj uses the Object class to enable any type to be passed, while Gen uses generics:
首先, 看一下下面的类.Obj和Gen执行同样的任务,但是Obj使用Object类型来允许任意类型的传递,而Gen则使用泛型。
' VB
Class Obj
Public V1 As Object
Public V2 As Object
Public Sub New(ByVal _V1 As Object, ByVal _V2 As Object)
V1 = _V1
V2 = _V2
End Sub
End Class
Class Gen(of T, U)
Public V1 As T
Public V2 As U
Public Sub New(ByVal _V1 As T, ByVal _V2 As U)
V1 = _V1
V2 = _V2
End Sub
End Class
// C#
class Obj
{
public Object t;
public Object u;
public Obj(Object _t, Object _u)
{
t = _t;
u = _u;
}
}
class Gen<T, U>
{
public T t;
public U u;
public Gen(T _t, U _u)
{
t = _t;
u = _u;
}
}
As you can see, the Obj class has two members of type Object. The Gen class has two members of type T and U. The consuming code will determine the types for T and U. Depending on how the consuming code uses the Gen class, T and U could be a string, an int, a custom class, or any combination thereof.
象你看到的,这个Obj类有两个Object类型的成员。这个Gen类有两个分别为T和U类型的成员。而Gen类中的赋值代码段(Gen中加粗体+加斜体红色的部分。在Gen类中是它的构造函数)将决定T和U的类型,依靠这段赋值代码段可以使T和U转换为一个字符串,一个整数,一个自定义类,或者任何其他组合。
There is a significant limitation to creating a generic class: generic code is valid only if it will compile for every possible constructed instance of the generic, whether an Int, a string, or any other class. Essentially, you are limited to the capabilities of the base Object class when writing generic code. Therefore, you could call the ToString or GetHashCode method within your class, but you could not use the + or > operator. These same restrictions do not apply to the consuming code because the consuming code has declared a type for the generic.
在建立一个泛型类时有一个重要的局限性:就是泛型代码总是正确的。因为,无论是一个Int,一个string,或者任何其他类,只要构造的是泛型实例,那它就是可以编译通过的。原则上,当编写泛型代码时,你的类能实现的功能会受到Object基类的约束。所以,在你的类中,你可以调用ToString()或GetHashCode方法,却不能使用 + 或 >操作符。但这些相同的约束不能应用到赋值代码段中,因为赋值代码段中存在了被声明为泛型的类型。
When you consume a generic type, you must specify the types for any generics used. Consider the following console application code, which uses the Gen and Obj classes:
当你实例化一个泛型类型时,你必须指定这个泛型类型要使用的具体类型。考虑下面代码是如何使用Gen和Obj类的。
' VB
' Add two Strings using the Obj class
Dim oa As Obj = New Obj("Hello, ", "World!")
Console.WriteLine(CType(oa.V1, String) + CType(oa.V2, String))
' Add two Strings using the Gen class
Dim ga As New Gen(Of String, String)("Hello, ", "World!")
Console.WriteLine(ga.V1 + ga.V2)
' Add a Double and an Integer using the Obj class
Dim ob As Obj = New Obj(10.125, 2005)
Console.WriteLine(CType(ob.V1, Double) + CType(ob.V2, Integer))
' Add a Double and an Integer using the Gen class
Dim gb As New Gen(Of Double, Integer)(10.125, 2005)
Console.WriteLine(gb.V1 + gb.V2)
// C#
// Add two strings using the Obj class
Obj oa = new Obj("Hello, ", "World!");
Console.WriteLine((string)oa.t + (string)oa.u);
// Add two strings using the Gen class
Gen<string, string> ga = new Gen<string, string>("Hello, ", "World!");
Console.WriteLine(ga.t + ga.u);
// Add a double and an int using the Obj class
Obj ob = new Obj(10.125, 2005);
Console.WriteLine((double)ob.t + (int)ob.u);
// Add a double and an int using the Gen class
Gen<double, int> gb = new Gen<double, int>(10.125, 2005);
Console.WriteLine(gb.t + gb.u);
If you run that code in a console application, the Obj and Gen classes produce exactly the same results. However, the code that uses the Gen class actually works faster because it does not require boxing and unboxing to and from the Object class. Additionally, developers would have a much easier time using the Gen class. First, developers would not have to manually cast from the Object class to the appropriate types. Second, type errors would be caught at compile time rather than at run time. To demonstrate that benefit, consider the following code, which contains an error:
如果你运行上面这段代码,这个Obj和Gen类都输出了正确的、并且相同的结果。可是,Gen类的代码运行的更快些,因为它不需要装箱和拆箱过程。另外,开发人员使用Gen类会感觉非常轻松。首先,开发人员不需要手动传递Object给适当的类型。第二,类型错误将会在编译时就被捕获。考虑下面代码,它包含了一个错误,用来示范这个优点。
' VB
' Add a Double and an Integer using the Gen class用一个double和一个integer实例化Gen类。
Dim gb As New Gen(Of Double, Integer)(10.125, 2005)
Console.WriteLine(gb.V1 + gb.V2)
' Add a Double and an Integer using the Obj class用一个double和一个integer实例化Obj类。
Dim ob As Obj = New Obj(10.125, 2005)
Console.WriteLine(CType(ob.V1, Integer) + CType(ob.V2, Integer))
// C#
// Add a double and an int using the Gen class用一个double和一个integer实例化Gen类。
Gen<double, int> gc = new Gen<double, int>(10.125, 2005);
Console.WriteLine(gc.t + gc.u);
// Add a double and an int using the Obj class用一个double和一个integer实例化Obj类。
Obj oc = new Obj(10.125, 2005);
Console.WriteLine((int)oc.t + (int)oc.u);
The last line in that code sample contains an error—the oc.t value is cast to an Int instead of to a double. Unfortunately, the compiler won't catch the mistake. Instead, in C#, a run-time exception is thrown when the runtime attempts to cast a double to an Int value. In Visual Basic, which allows narrowing conversions by default, the result is even worse-a miscalculation occurs. It's much easier to fix a bug that the compiler catches and much harder to detect and fix a run-time error, so the generic class provides a clear benefit.
在上面例子代码的最后一行包含了一个错误-将oc.t的double值转换为int类型。不幸的是,编译器无法捕获这个错误。在C#中,只有当运行时试图去将一个double转换为int类型时,才会抛出运行时错误。而在VB中,默认是允许这种类型转换的,结果更糟糕,将会导致错误计算(计算结果有误差)。尽早的确定这个bug并在编译器中捕获它,与一个难以察觉并确定的运行时错误相比,泛型类提供了一个明确的好处。
Generics would be extremely limited if you could only write code that would compile for any class, because you would be limited to the capabilities of the base Object class. To overcome this limitation, use constraints to place requirements on the types that consuming code can substitute for your generic.
假设你仅仅是编写类的编译代码的话,泛型将会受到强大的限制。因为,你只具有Object基类的功能。那么,如何克服这种限制呢,答案是使用“约束“。
Generics support four types of constraints:泛型支持四种约束类型。
· Interface Allow only types that implement specific interfaces to use your generic.
· 接口约束只有指定接口或实现了指定接口的类才能作为参数实例化你的泛型。
· Base class Allow only types that match or inherit from a specific base class to use your generic.
· 基类约束 只有指定基类或者继承于这个指定基类的类才能作为参数实例化你的泛型
· Constructor Require types that use your generic to implement a parameterless constructor.
· 构造函数约束 无参数的构造函数才能作为参数实例化你的泛型
· Reference or value type Require types that use your generic to be either a reference or value type.
· 引用类型或值类型约束任何一个引用类型或值类型,均可作为参数实例化你的泛型。
Use the As clause in Visual Basic or the where clause in C# to apply a constraint to a generic. For example, the following generic class could be used only by types that implement the IComparable interface:
在VB中使用AS子句,在C#中使用where子句,来将约束应用到一个泛型上。例如,下面这个泛型类,只有实现了IComparable接口的类才能实例化它。
' VB
Class CompGen(Of T As IComparable)
Public t1 As T
Public t2 As T
Public Sub New(ByVal _t1 As T, ByVal _t2 As T)
t1 = _t1
t2 = _t2
End Sub
Public Function Max() As T
If t2.CompareTo(t1) < 0 Then
Return t1
Else
Return t2
End If
End Function
End Class
// C#
class CompGen<T>
where T : IComparable
{
public T t1;
public T t2;
public CompGen(T _t1, T _t2)
{
t1 = _t1;
t2 = _t2;
}
public T Max()
{
if (t2.CompareTo(t1) < 0)
return t1;
else
return t2;
}
}
The preceding class will compile correctly. However, if you remove the where clause, the compiler will return an error indicating that generic type T does not contain a definition for CompareTo. By constraining the generic to classes that implement IComparable, you guarantee that the CompareTo method will always be available.
上面描述的这个类可以正常编译。可是,假设你不使用where子句,那么编译器将返回一个错误,指出这个泛型类型T没有包含一个CompareTo的定义。通过对泛型的约束,强制它实现IComparable接口,你可以保证这个CompareTo方法始终保持有效。
Most projects are nonlinear. In Windows Forms applications, you might have to wait for a user to click a button or press a key, and then respond to that event. In server applications, you might have to wait for an incoming network request. These capabilities are provided by events in the .NET Framework, as described in the following sections.
大部分的工程都是相互联系的。在window forms 应用程序中,你可以等待一个用户点击一个按钮或按下某个按键,然后用事件来响应它。而在服务器应用程序,你等待的是一个来自网络的请求。这些功能在.net中都是通过事件来提供。下面部分将对事件进行描述。
An event is a message sent by an object to signal the occurrence of an action. The action could be caused by user interaction, such as a mouse click, or it could be triggered by some other program logic. The object that raises the event is called the event sender. The object that captures the event and responds to it is called the event receiver.
事件是对象发送的一个消息,这个消息作为信号来产生一个动作。这个动作通过与用户进行交互来引发。例如,一次鼠标点击,或者通过其他一些程序逻辑被触发。这个激活事件的对象称为事件发送者(event sender)。捕获并响应事件的对象称为事件接受者。
In event communication, the event sender class does not know which object or method will receive (handle) the events it raises. What is needed is an intermediary (or pointer-like mechanism) between the source and the receiver. The .NET Framework defines a special type (Delegate) that provides the functionality of a function pointer.
在事件通信中,事件发送者无法知道哪个对象或方法将会接收事件。在事件发送者和事件接受者需要的一个中间媒介(类似指针机制)是什么呢?.net框架中定义了一个特定类型(Delegate),由它提供一个类似函数指针的功能。
A delegate is a class that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and it can hold references only to methods that match its signature. A delegate is thus equivalent to a type-safe function pointer or a callback. While delegates have other uses, the discussion here focuses on the event-handling functionality of delegates. A delegate declaration is sufficient to define a delegate class. The declaration supplies the signature of the delegate, and the common language runtime provides the implementation. The following example shows an event delegate declaration:
代理(委托)是一个类,它控制方法中的引用。与其它类不同的是,代理(委托)类有一个签名,这个签名用来控制方法中的引用并对其进行匹配。代理(委托)相当于一个类型安全的指针或回调。代理(委托)还有其它的用处,不过在这里我们讨论的焦点是事件处理中代理的功能。
' VB
Public Delegate Sub AlarmEventHandler(sender As Object, e As EventArgs)
// C#
public delegate void AlarmEventHandler(object sender, EventArgs e);
The standard signature of an event handler delegate defines a method that does not return a value, whose first parameter is of type Object and refers to the instance that raises the event, and whose second parameter is derived from type EventArgs and holds the event data. If the event does not generate event data, the second parameter is simply an instance of EventArgs. Otherwise, the second parameter is a custom type derived from EventArgs and supplies any fields or properties needed to hold the event data.
一个具有标准签名的代理的定义是一个没有返回值的方法,这个方法只包含两个参数,第一个是引起这个事件的实例的Object类型的引用,而另一个参数是派生于EventArgs,它保存着传入事件的参数数据。如果事件本身不具有事件数据,那么这第二个参数只是一个简单的EventArgs实例。另外,这第二个参数是派生于EventArgs的自定义类型,它提供了各种字段和属性来获取事件数据。
EventHandler is a predefined delegate that specifically represents an event handler method for an event that does not generate data. If your event does generate data, you must supply your own custom event data type and either create a delegate where the type of the second parameter is your custom type, or you must use the generic EventHandler delegate class and substitute your custom type for the generic type parameter.
EventHandler是一个预定义的代理,它表现为一个没有数据的事件的事件处理方法。假设,你的事件具有事件数据,你必须提供自定义的事件数据类型,同时,你要么建立一个委托,这个委托的第二个参数是你自定义的类型,要么你必须使用泛型EventHandler代理类,并用你自定义的类型替换泛型类型参数。
To associate the event with the method that will handle the event, add an instance of the delegate to the event. The event handler is called whenever the event occurs, unless you remove the delegate.
若要将事件和事件处理方法进行关联,需要添加代理的实例给事件。除非你删除了代理,否则只要事件发生,事件处理就会被调用。
You must do two things to respond to an event:你必须做两件事情
· Create a method to respond to the event. The method must match the Delegate signature. Typically, this means it must return void and accept two parameters: an Object and an EventArgs (or a derived class). The following code demonstrates this:
· 建立一个响应事件的方法。这个方法必须和代理的签名匹配。这句话的意思是,这个方法的返回类型必须是void类型,并且具有两个参数:分别是Object和EventArgs(或者派生与EventArgs)。下面是代码示例:
· ' VB
· Public Sub Button1_Click(sender As Object, e As EventArgs)
· ' Method code
· End Sub
·
· // C#
· private void button1_Click(object sender, EventArgs e)
· {
· // Method code
· }
· Add the event handler to indicate which method should receive events, as the following code demonstrates:
· 添加事件处理代码用来指出哪个方法将会接收事件。
· ' VB
· AddHandler Me.Button1.Click, AddressOf Me.Button1_Click
·
· // C#
· this.button1.Click += new System.EventHandler(this.button1_Click);
|
.NET 2.0 |
The .NET Framework 2.0 includes a new generic version of the EventHandler type. Net2.0框架包含一个新的泛型版本的EventHandler类型。 |
When the event occurs, the method you specified will run.
然后,当事件产生时,上面这个你指定的方法将被调用并运行。
At a minimum, you must do three things to raise an event:在最短的时间内,你要做三件事用来触发事件。
· Create a delegate:建立一个代理
· ' VB
· Public Delegate Sub MyEventHandler(ByVal sender As Object, ByVal e As EventArgs)
·
· // C#
· public delegate void MyEventHandler(object sender, EventArgs e);
· Create an event member:建立一个事件成员
· ' VB
· Public Event MyEvent As MyEventHandler
·
· // C#
· public event MyEventHandler MyEvent;
· Invoke the delegate within a method when you need to raise the event, as the following code demonstrates:
· 当你需要触发事件时,在方法中调用代理。
· ' VB
· Dim e As EventArgs = New EventArgs
· RaiseEvent MyEvent(Me, e)
·
· // C#
· MyEventHandler handler = MyEvent;
· EventArgs e = new EventArgs();
·
· if (handler != null)
· {
· // Invokes the delegates.
· handler(this, e);
· }
· // Note that C# checks to determine whether handler is null.
· //注意:C#中,无论handler是否为null,都会对其进行检查。
· // This is not necessary in Visual Basic
· //而在VB中这不是必须的。
Additionally, you can derive a custom class from EventArgs if you need to pass information to the event handler.
另外,假设你需要向事件处理程序传递信息,你可以自定义一个EventArgs的派生类。
|
Differences in raising events in Visual Basic and C# VB和C#在触发事件上的区别 |
Visual Basic and C# differ when raising events. In C#, you must check whether the event is null before calling it. In Visual Basic, you can omit that check. 当触发事件时,VB和C#是不同的。在C#中调用事件之前,无论事件是否为null,你都必须对其进行检查确认。而在VB中,这项检查是可以忽略的。 |
Attributes describe a type, method, or property in a way that can be programmatically queried using a technique called Reflection. Some common uses for attributes are to
属性,某种程度上来说,它用来描述一个类型、方法或特性,可以使用一种叫反射(Reflection)的技术来查询它的目录。下面是一些属性的常用方式。
· Specify which security privileges a class requires
· 指定一个类需要的安全特性
· Specify security privileges to refuse to reduce security risk
· 指定安全特性,减少安全隐患
· Declare capabilities, such as supporting serialization
· 声明功能,例如支持序列化
· Describe the assembly by providing a title, description, and copyright notice
· 描述程序集标题、描述、版权信息。
Attribute types derive from the System.Attribute base class and are specified using <> or [] notation. The following code sample demonstrates how to add assembly attributes:
属性类型派生于System.Attribute基类,通过指定<>或[]符号来使用。下面示范如何向程序集添加数据。
' VB AssemblyInfo.vb
<Assembly: AssemblyTitle("ch01vb")>
<Assembly: AssemblyDescription("Chapter 1 Samples")>
<Assembly: AssemblyCompany("Microsoft Learning")>
<Assembly: AssemblyProduct("ch01vb")>
<Assembly: AssemblyCopyright("Copyright © 2006")>
<Assembly: AssemblyTrademark("")>
// C# - AssemblyInfo.cs
[assembly: AssemblyTitle("ch01cs")]
[assembly: AssemblyDescription("Chapter 1 Samples")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft Learning")]
[assembly: AssemblyProduct("ch01cs")]
[assembly: AssemblyCopyright("Copyright © 2006")]
[assembly: AssemblyTrademark("")]
Visual Studio automatically creates some standard attributes for your assembly when you create a project, including a title, description, company, guide, and version. You should edit these attributes for every project you create because the defaults do not include important information such as the description.
当你建立一个项目时,VS会为你的程序集自动建立一些标准属性,包括标题、描述、公司信息、指南和版本信息。你可以为每个建立的项目编辑这些属性,因为,项目建立时默认不包含象“描述”这样的重要信息。
Attributes do more than describe an assembly to other developers, they can also declare requirements or capabilities. For example, to enable a class to be serialized, you must add the Serializable attribute, as the following code demonstrates:
属性提供了更多的信息给其他开发人员,包括声明需要的条件或实现功能信息。例如,允许一个类序列化,你必须添加Serializable属性,示范代码如下:
' VB
<Serializable()> Class ShoppingCartItem
End Class
// C#
[Serializable]
class ShoppingCartItem
{
}
Without the Serializable attribute, a class is not serializable. Similarly, the following code uses attributes to declare that it needs to read the C:\boot.ini file. Because of this attribute, the runtime will throw an exception prior to execution if it lacks the specified privilege:
没有Serializable属性,类是不可以序列化的。同样的,下面代码使用属性来声明它需要读取“C:\boot.ini”文件。由于属性的定义,假设程序缺少指定权限,运行时会在执行前抛出一个异常。
' VB
Imports System.Security.Permissions
<Assembly: FileIOPermissionAttribute(SecurityAction.RequestMinimum, Read := "C:\boot.ini")>
Module Module1
Sub
Main
()
Console.WriteLine("Hello, World!")
End Sub
End Module
// C#
using System;
using System.Security.Permissions;
[assembly:FileIOPermissionAttribute(SecurityAction.RequestMinimum, Read=@"C:\boot.ini")]
namespace DeclarativeExample
{
class Class1
{
[STAThread]
static void
Main
(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
Type forwarding is an attribute (implemented in TypeForwardedTo) that allows you to move a type from one assembly (assembly A) into another assembly (assembly B), and to do so in such a way that it is not necessary to recompile clients that consume assembly A. After a component (assembly) ships and is being used by client applications, you can use type forwarding to move a type from the component (assembly) into another assembly and ship the updated component (and any additional assemblies required), and the client applications will still work without being recompiled. Type forwarding works only for components referenced by existing applications. When you rebuild an application, there must be appropriate assembly references for any types used in the application.
类型传递是一个属性(在TypeForwardedTo中实现),它允许你将一个类型从一个程序集(程序集A)移动到另一个程序集(程序集B),并且在客户端实例化程序集A时不需要重新编译,就可以运行。在一个组件(程序集)载入并被客户端应用程序使用后,你可以用类型传递将组件中一个类型移动到另一个程序集,而客户端应用程序仍将保持工作,不需要重新编译。类型传递只能使用在从已存在的应用程序引用的组件。当你重新编译一个应用程序时,在应用程序中使用的任何类型都必须是恰当的程序集引用(这个程序集已存在)。
To move a type from one class library to another, follow these steps:
下面这些步骤用来将一个类型从一个类库移动到另一个。
|
.NET 2.0 |
Type forwarding is a new feature in .NET 2.0. 类型传递是net2.0框架提供的一个新的特性 |
1. Add a TypeForwardedTo attribute to the source class library assembly.
1.添加一个TypeForwardedTo属性到来源程序集类库。
2. Cut the type definition from the source class library.
2.将类型声明代码剪切
3. Paste the type definition into the destination class library.
3.将剪切的类型声明代码粘贴到目的类库。
4. Rebuild both libraries.
4.编译两个类库
The following code shows the attribute declaration used to move TypeA to the DestLib class library:
下面代码示范将TypeA移动到DestLib类库的属性声明。
' VB
Imports System.Runtime.CompilerServices
<Assembly:TypeForwardedTo(GetType(DestLib.TypeA))]>
// C#
using System.Runtime.CompilerServices;
[assembly:TypeForwardedTo(typeof(DestLib.TypeA))]
The following exercises demonstrate inheritance and events. If you encounter a problem completing an exercise, the completed projects are available on the companion CD in the Code folder.
下面的练习示范继承和事件。如果你在练习中发现了问题,你可以利用配套CD的Code目录中已经完成的项目。
Exercise 1: Derive a New Class from an Existing Class 练习1:从一个存在的类派生一个新类
|
In this exercise, you will derive a new class from the Person class you created in Lesson 1.
在这个练习中,你将从你在第一课中建立的Penson类派生一个新类
1. Copy the Chapter01\Lesson3-Person folder from the companion CD to your hard disk, and open either the C# version or the Visual Basic version of the CreateStruct project.
1.从配套CD中拷贝Chapter01\Lesson3-Person目录到你的硬盘,打开C#
或VB 版本的CreateStruct项目。
2. Change the Person structure to a class.
2.将Person结构改为Person类
3. Create a new class definition named Manager that inherits from the base Person class.
3.建立一个名叫Manager的新类,它继承于Person类
4. ' VB
5. Class Manager
6. End Class
7.
8. // C#
9. class Manager : Person
10. {
11. }
12.Add two new public members as strings: phoneNumber and officeLocation.
12.添加两个字符串类型的公共成员:phoneNumber和officeLocation
13.Override the constructor to accept a phone number and office location to define the new members. You will need to call the base class's constructor, as shown in the following code sample:
13.重写构造函数,使它可以接收电话号码和办公地址并赋给上面两个新的成员。同时你还需要调用基类的构造函数。如下代码例子:
14. ' VB
15. Public Sub New(ByVal _firstName As String, ByVal _lastName As String, _
16. ByVal _age As Integer, ByVal _gender As Genders, ByVal _phoneNumber As String, _
17. ByVal _officeLocation As String)
18. MyBase.New(_firstName, _lastName, _age, _gender)
19. phoneNumber = _phoneNumber
20. officeLocation = _officeLocation
21. End Sub
22.
23. // C#
24. public Manager(string _firstName, string _lastName, int _age,
25. Genders _gender, string _phoneNumber, string _officeLocation)
26. : base (_firstName, _lastName, _age, _gender)
27. {
28. phoneNumber = _phoneNumber;
29. officeLocation = _officeLocation;
30. }
31.Override the ToString method to add the phone number and office location, as shown in the following sample:
31.重写ToString方法增加电话号码和办公地址的输出。
32. ' VB
33. Public Overloads Overrides Function ToString() As String
34. Return MyBase.ToString + ", " + phoneNumber + ", " + officeLocation
35. End Function
36.
37. // C#
38. public override string ToString()
39. {
40. return base.ToString() + ", " + phoneNumber + ", " + officeLocation;
41. }
42.Modify the Main method to create a Manager object instead of a person object. Then run your application to verify that it works correctly.
42.修改Main方法,建立一个Manager对象替换Person对象。然后运行你的应用程序,验证它的正确性。
|
|
Exercise 2: Respond to an Event 练习2:事件响应
|
In this exercise, you will create a class that responds to a timer event.
在这个练习中,你将建立一个类,用来响应一个timer(定时器)事件
1. Using Visual Studio, create a new Windows Forms application project. Name the project TimerEvents.
1.使用VS建立一个名叫TimerEvents的WindowForms应用程序项目
2. Add a ProgressBar control to the form, as shown in Figure 1-2.
3. 向窗体添加一个ProgressBar(进度条)控件,如图1-2
Figure 1-2: You will control this progress bar by responding to timer events
图1-2通过响应的timer事件控制进度条
4. Within the form class declaration, declare an instance of a System.Windows.Forms.Timer object. Timer objects can be used to throw events after a specified number of milliseconds. The following code sample shows how to declare a Timer object:
3.在窗体定义中,声明一个System.Window.Forms.Timer对象的实例。Timer(定时器)对象用于在指定的毫秒后产生事件。下面代码示范如何声明一个Timer(定时器)对象。
5. ' VB
6. Dim t As Timer
7.
8. // C#
9. System.Windows.Forms.Timer t;
10.In the designer, view the properties for the form. Then view the list of events. Double-click the Load event to automatically create an event handler that will run the first time the form is initialized. Within the method, initialize the Timer object, set the interval to one second, create an event handler for the Tick event, and start the timer. The following code sample demonstrates this:
9.在设计器中,查看窗体属性。然后,查看事件列表。双击Load事件,将会自动建立一个事件处理程序,它将在窗体初始化时第一次运行。在方法中,初始化Timer对象,设置interval(时间间隔)属性为1秒,建立Tick事件处理程序,并启动timer。示例代码如下:
11. ' VB
12. Private Sub Form1_Shown(ByVal sender As System.Object, ByVal e As System.EventArgs) _
13. Handles MyBase.Shown
14. t = New System.Windows.Forms.Timer
15. t.Interval = 1000
16. AddHandler t.Tick, AddressOf Me.t_Tick
17. t.Start()
18. End Sub
19.
20. // C#
21. private void Timer_Shown(object sender, EventArgs e)
22. {
23. t = new System.Windows.Forms.Timer();
24. t.Interval = 1000;
25. t.Tick += new EventHandler(t_Tick);
26. t.Start();
27. }
28.Implement the method that will respond to the Timer.Tick event. When the event occurs, add 10 to the ProgressBar.Value attribute. Then stop the timer if the ProgressBar.Value attribute has reached 100. The following code sample demonstrates this:
27.实现Timer.Tick事件响应方法。当事件产生时,将ProgressBar.Value属性值加十。然后,判断如果ProgressBar.Value属性值到达100,则停止timer。
29. ' VB
30. Private Sub t_Tick(ByVal sender As Object, ByVal e As EventArgs)
31. ProgressBar1.Value += 10
32.
33. If ProgressBar1.Value = 100 Then
34. t.Stop()
35. End If
36. End Sub
37.
38. // C#
39. void t_Tick(object sender, EventArgs e)
40. {
41. progressBar.Value += 10;
42.
43. if (progressBar.Value >= 100)
44. t.Stop();
45. }
46.Run the application to verify that it responds to the timer event every second.
45.运行应用程序验证每秒timer产生的事件响应。
|
· Use inheritance to create new types based on existing ones.
· 通过继承,建立一个基于一个存在的类的新类。
· Use interfaces to define a common set of members that must be implemented by related types.
· 使用接口来定义一个公共成员集,凡是和它有关系的类型必须实现这个公共成员集。
· Partial classes split a class definition across multiple source files.
· 飞类允许你将一个类的定义进行分割,并将各个部分放到多个源文件中。
· Events allow you to run a specified method when something occurs in a different section of code.
· 在不同的代码部分,当有事情发生时,事件运行你指定的方法进行处理。
· Use attributes to describe assemblies, types, and members.
· 利用属性去描述程序集、类型和成员
· Use the TypeForwardedTo attribute to move a type from one class library to another.
· 利用TypeForwardedTo属性将一个类型从一个类库移动到另一个。
You can use the following questions to test your knowledge of the information in Lesson 3, "Constructing Classes." The questions are also available on the companion CD if you prefer to review them in electronic form.
你可以使用下面的问题来测试你在本课中学到的知识。你也可以利用配套CD中的电子版。
|
Answers |
Answers to these questions and explanations of why each answer choice is right or wrong are located in the "Answers" section at the end of the book. 回答这些问题,并解释每个答案对错的原因。选项的错对可以查找在书后的“答案”部分. |
Answers
Correct Answers: B and C 正确 A. Incorrect: Interfaces define a contract between types; inheritance derives a type from a base type. A.错误:接口定义了类型之间的约束;继承是从一个基类派生一个类型。 B. Correct: Interfaces define a contract between types, ensuring that a class implements specific members. B.正确:接口定义类型之间的约束,确保每个类都必须实现指定的成员 C. Correct: Inheritance derives a type from a base type, automatically implementing all members of the base class, while allowing the derived class to extend or override the existing functionality. C.正确:继承是从一个基类派生一个类型,自动实现基类的所有成员,允许派生类扩展或重写存在的功能。 D. Incorrect: Interfaces define a contract between types; inheritance derives a type from a base type. D.错误:接口定义了类型之间的约束;继承是从一个基类派生一个类型。 |
|
Correct Answers: A and C A. Correct: Nullable is a generic type.正确:Nullable是一个泛型类型 B. Incorrect: Boolean is a nongeneric value type.错误:Boolean是一个非泛型的值类型。 C. Correct: EventHandler is a generic type.正确:EventHandler是一个泛型类型 D. Incorrect: System.Drawing.Point is a structure and is not generic.错误:System.Drawing.Point是一个结构并且不是泛型结构。 |
|
Correct Answer: D A. Incorrect: The Object class does not have a Dispose member method. Additionally, you would need to use a constraint to mandate types implementing the IDisposable interface to call the Dispose method. A.错误:Object类没有Dispose方法。另外,你需要使用约束要求类型实现IDisposeable接口,并调用Dispose方法。 B. Incorrect: Implementing an interface does not enable generic types to use interface methods. B.错误:实现接口并不能让泛型类型使用接口方法。 C. Incorrect: Deriving the generic class from an interface does not enable generic types to use interface methods. C.错误:从一个接口派生这个泛型类,并不能让泛型类型使用接口方法 D. Correct: If you use constraints to require types to implement a specific interface, you can call any methods used in the interface. D.正确:如果你使用约束要求类型实现一个指定接口,你可以调用接口中的任何方法并使用。 |
|
Correct Answer: A A. Correct: Delegates define the signature (arguments and return type) for the entry point. A.正确:代理为输入点定义签名(参数和返回类型) B. Incorrect: Event procedures can be Shared/static or instance members. B.错误:事件程序可以被Shared/static或实例化成员 C. Incorrect: If you mistyped the event procedure name, you would receive a different compiler error. C.错误:如果你用错了事件处理程序的名字,你得到的将是一个不同的错误。 D. Incorrect: Events work equally well, regardless of the language used. D.错误:事件正常工作与使用的语言无关 |