C#基础教程(下) C#基础教程(下) 2009-09-16 10:09 C#教程第六课:名称空间 本节课将介绍C#的名称空间。其目的是: 1.了解什么是名称空间。 2.了解如何实现"using"指示符。 3.了解"alias" 指示符的用法。 4.了解名称空间的成员的内容。 在第一课中,你已经在简单的hello程序中看到了"using System;"指示符的使用。该指示符可以让你使用System名称空间中的成员。在第一课中,未及对此作出详细介绍,现在我们来解释一下名称空间的具体用法。一旦学完了本节课,你将了解"using"指示符及其相关内容。 作为C#的元素,名称空间可以用来帮助组织程序的结构,可以避免两套代码集中命名的冲突。在程序代码中,使用名称空间是个良好的编程习惯,因为这有助于重用你的程序代码。 1.清单6-1. The C# Station Namespace: NamespaceCSS.cs // Namespace Declaration using System; // The C# Station Namespace namespace csharp_station { // Program start class class NamespaceCSS { // Main begins program execution. public static void Main() { // Write to console Console.WriteLine("This is the new C# Station Namespace."); } } } 说明 清单6-1演示了如何创建一个名称空间。把单词"namespace"放在"csharp_station"之前,就创建了一个名称空间。"csharp_station"名称空间内的大括号中包含了成员。 2.清单6-2 Nested Namespace 1: NestedNamespace1.cs // Namespace Declaration using System; // The C# Station Tutorial Namespace namespace csharp_station { namespace tutorial { // Program start class class NamespaceCSS { // Main begins program execution. public static void Main() { // Write to console Console.WriteLine("This is the new C# Station Tutorial Namespace."); } } } } 说明 名称空间可以建立一个代码的组织结构。一个良好的编程习惯是:用层次模式来组织你的名称空间。你可以把通用一些的名称放在最顶层,里层则放置一些专用一些的名称。这个层次系统可以用嵌套的名称空间表示。清单6-2演示了如何建立一个嵌套的名称空间。在不同的子名称空间内放置代码,从而组织好你的代码的结构。 3.清单6-3. Nested Namespace 2: NestedNamespace2.cs // Namespace Declaration using System; // The C# Station Tutorial Namespace namespace csharp_station.tutorial { // Program start class class NamespaceCSS { // Main begins program execution. public static void Main() { // Write to console Console.WriteLine("This is the new C# Station Tutorial Namespace."); } } } 说明 清单6-3演示了另外一种编写嵌套的名称空间的方法。在"csharp_station"和"tutorial"之间置入点运算符,表明这是嵌套的名称空间。结果同清单6-2。 相比而言,清单6-3 更易
书
关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf
写。 4.清单6-4. Calling Namespace Members: NamespaceCall.cs // Namespace Declaration using System; namespace csharp_station { // nested namespace namespace tutorial { class myExample1 { public static void myPrint1() { Console.WriteLine("First Example of calling another namespace member."); } } } // Program start class class NamespaceCalling { // Main begins program execution. public static void Main() { // Write to console tutorial.myExample1.myPrint1(); csharp_station.tutorial.myExample2.myPrint2(); } } } // same namespace as nested namespace above namespace csharp_station.tutorial { class myExample2 { public static void myPrint2() { Console.WriteLine("Second Example of calling another namespace member."); } } } 说明 1.清单6-4 的例子演示了用完整的名称指示,调用名称空间的成员。 一个完整的名称指示包括名称空间名,以及调用的方法名。程序的上半部分,在"csharp-station"名称空间内嵌套的名称空间"tutorial"中,定义了类"myExample1"及其方法"myPrint1"。 Main()方法中用完整的名称指示:"tutorial.myExample1.myPrint()" 来进行调用。 因为Main()方法和tutorial名称空间位于同一名称空间内,如果使用"csharp_station"的全称不是必需的。 2.清单6-4的下半部分,也是名称空间"csharp_station.tutorial"的一部分。 类"myExample1"和"myExample2"都属于该名称空间。另外,也可以把它们分别写入不同的文件,同时它们仍然属于同一名称空间。在Main()方法中,调用"myPrint2"方法时,采用了全称:"csharp_station.tutorial.myExample2.myPrint2()"。 在这里,必须使用全称中"csharp_station",因为"myExample2"定义在外部。 3.注意:这里对两个不同的类起了不同的名字: "myExample1"和"myExample2"这是因为对于每个名称空间来说,其中的成员必须有唯一的名称。 记住:它们都处于同一名称空间中,不能取名相同。方法"myPrint1"和"myPrint2" 名称的不同仅仅是为了方便起见,即使同名也没有问
题
快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题
,因为它们属于不同的类。 5.清单6-5. The using Directive: UsingDirective.cs // Namespace Declaration using System; using csharp_station.tutorial; // Program start class class UsingDirective { // Main begins program execution. public static void Main() { // Call namespace member myExample.myPrint(); } } // C# Station Tutorial Namespace namespace csharp_station.tutorial { class myExample { public static void myPrint() { Console.WriteLine("Example of using a using directive."); } } 说明 调用方法时,如果你不想打入全称,可使用"using"指示符。在清单6-5中,有两个"using"指示符。第一个指示符是"using System",同本教程其它地方出现的"using"指示符相同。你不需要每次都打上"System",只需要打入该名称空间的成员方法名即可。在myPrint()中,"Console"是个"System"名称空间中的成员类,该类有个"WriteLine"的方法。该方法的全称是: "System.Console.WriteLine(...)"。 类似地,using指示符"using csharp_station.tutorial"可以让我们在使用 "csharp_station.tutorial" 名称空间的成员时,无需打入全称。所以,我们可以打入"myExample.myPrint()"。如果不使用"using"指示符,每次实现该方法时,我们就得打入"csharp_station.tutorial.myExample.myPrint()" 。 6.清单6-6. The Alias Directive: AliasDirective.cs // Namespace Declaration using System; using csTut = csharp_station.tutorial.myExample; // alias // Program start class class AliasDirective { // Main begins program execution. public static void Main() { // Call namespace member csTut.myPrint(); myPrint(); } // Potentially ambiguous method. static void myPrint() { Console.WriteLine("Not a member of csharp_station.tutorial.myExample."); } } // C# Station Tutorial Namespace namespace csharp_station.tutorial { class myExample { public static void myPrint() { Console.WriteLine("This is a member of csharp_station.tutorial.myExample."); } } } 说明 1.有时,往往遇到取名较长的名称空间,而你可以把该名称变短些。 这样就增强了可读性,还避免了同名的冲突。清单6-6 演示了如何使用别名指示符,创建别名的格式例子是:"using csTut = csharp_station.tutorial.myExample"。表达式"csTut"可以取代"csharp_station.tutorial.myExample",用在本文件的任何地方。在Main()方法中就使用了"csTut"。 2.在Main()方法中,调用了"AliasDirective" 类中"myPrint" 方法。 这与"myExample" 类的"myPrint"方法同名。 虽然同名,这两个方法都各自正确地进行了调用,原因是:"myExample"类的"myPrint"方法用别名"csTut"表示。编译器能够准确地了解所要执行的是哪个方法。一旦漏掉了"csTut",编译器将两次调用"AliasDirective"类的"myPrint"方法。 3.另外一方面,如果我们没有创建别名指示符,而是添加了"using csharp_station.tutorial.myExample"之后,再调用myPrint(),编译器就会生成出错信息,因为它不知道究竟是调用. "csharp_station.tutorial.myExample.myPrint()"方法呢?还是去调用"AliasDirective.myPrint()"方法。所以使用名称空间是良好的编程习惯,可避免代码中的冲突现象。 小结 到现在为止,我们已经了解在名称空间中可以使用类,实际上,名称空间可以使用如下类型的数据: 类;结构;接口;枚举;代理 在后面的课程中我们将详细介绍这些数据类型。 概括来讲,你已经了解了什么是名称空间,如何定义自己的名称空间。如果你不想打入全称,可以使用"using"指示符。一旦你想缩短名称空间的长名,可以使用别名指示符。另外,除了类之外,你也了解了名称空间可以使用的其他一些数据类型。 C#教程第七课:类的入门 OutputClass类的多个实例。这些实例都是各自独立的。例如,OutputClass类的两个实例创建如下: OutputClass oc1 = new OutputClass("OutputClass1"); OutputClass oc2 = new OutputClass("OutputClass2"); 于是,创建了OutputClass类的两个单独的实例,且各自带有单独的"myString"域和"printString()"方法。上例中,两个实例名为"oc1" 和"oc2"。 另外一方面,如果类成员是静态的,可以通过如下格式来访问:
.。 一旦OutputClass类有如下的静态方法: static void staticPrinter() { Console.WriteLine("There is only one of me."); } 你就可以用下面的方式,从Main()中调用该函数: OutputClass.staticPrinter(); 注意: 调用类的静态成员必须通过类名而不是实例名。类的静态成员的副本仅有一个。 另外一种类型的构造函数是静态构造函数。 通过在构造函数名称的前面使用关键字"static",就可以定义一个静态的构造函数。 调用静态的构造函数的发生时间是:在创建类的实例之前 ,在调用类的静态成员之前,在调用派生类的静态构造函数之前。(在后续课程中将介绍),且仅被调用一次。 OutputClass也有一个析构函数,除了前面加上了"~"符号,就跟构造函数的格式一样。析构函数用于释放类所占用的资源。当C#垃圾搜集器决定把对象从内存中清除出去时,就会调用析构函数。 小结 现在,你已经了解了类的如下成员:域,方法,构造函数,析构函数。下面是类的完整的成员类型: 构造函数;析构函数;域;方法;属性;索引;代理 ;事件;嵌套类 上面没有介绍过的类型将在后续课程中讲解。 概括地讲,你现在已经学会了如何定义常规的和静态的构造函数,也了解了如何初始化类的域。如果没有必要实例化一个对象,可以创建静态的类成员。你也了解了用来释放资源的析构函数的用法。。 C#教程第八课:类的继承 本节课将介绍C#中的继承,其目的如下: 1.基类的实现 2.类的继承 3.在派生类中初始化基类 4.如何调用基类成员 5.如何覆盖基类成员 继承是面向对象程序设计的主要特征之一,它可以让你重用代码,可以节省程序设计的时间。 1.清单8-1 继承: BaseClass.cs using System; public class ParentClass { public ParentClass() { Console.WriteLine("Parent Constructor."); } public void print() { Console.WriteLine("I''m a Parent Class."); } } public class ChildClass : ParentClass { public ChildClass() { Console.WriteLine("Child Constructor."); } public static void Main() { ChildClass child = new ChildClass(); child.print(); } } Output: Parent Constructor. Child Constructor. I''m a Parent Class. 说明 清单8-1演示了两个类的用法。上面的一个类名为ParentClass, main函数中用到的类名为ChildClass。要做的是创建一个使用父类ParentClass现有代码的子类ChildClass。 1.首先必须说明ParentClass是ChildClass的基类。 这是通过在ChildClass类中作出如下说明来完成的:"public class ChildClass : ParentClass"。在派生类标识符后面,用分号":" 来表明后面的标识符是基类。C#仅支持单一继承。因此,你只能指定一个基类。 2.ChildClass的功能几乎等同于ParentClass。 因此,也可以说ChildClass "就是" ParentClass。在ChildClass 的Main()方法中,调用print() 方法的结果,就验证这一点。该子类并没有自己的print()方法,它使用了ParentClass中的 print()方法。在输出结果中的第三行可以得到验证。 3.基类在派生类初始化之前自动进行初始化。 注意到清单8-1的输出结果。ParentClass 类的构造函数在ChildClass的构造函数之前执行。 2.清单 8-2. 派生类同基类进行通信: BaseTalk.cs using System; public class Parent { string parentString; public Parent() { Console.WriteLine("Parent Constructor."); } public Parent(string myString) { parentString = myString; Console.WriteLine(parentString); } public void print() { Console.WriteLine("I''m a Parent Class."); } } public class Child : Parent { public Child() : base("From Derived") { Console.WriteLine("Child Constructor."); } public void print() { base.print(); Console.WriteLine("I''m a Child Class."); } public static void Main() { Child child = new Child(); child.print(); ((Parent)child).print(); } } Output: From Derived Child Constructor. I''m a Parent Class. I''m a Child Class. I''m a Parent Class. 说明 1.派生类在初始化的过程中可以同基类进行通信。 清单8-2演示了在子类的构造函数定义中是如何实现同基类通信的。分号":"和关键字base用来调用带有相应参数的基类的构造函数。输出结果中,第一行表明:基类的构造函数最先被调用,其实在参数是字符串"From Derived"。 2.有时,对于基类已有定义的方法,打算重新定义自己的实现。 Child类可以自己重新定义print()方法的实现。Child的print()方法覆盖了Parent中的 print 方法。结果是:除非经过特别指明,Parent类中的print方法不会被调用。 3.在Child类的 print() 方法中,我们特别指明:调用的是Parent类中的 print() 方法。 方法名前面为"base",一旦使用"base"关键字之后,你就可以访问基类的具有公有或者保护权限的成员。 Child类中的print()方法的执行结果出现上面的第三行和第四行。 4.访问基类成员的另外一种方法是:通过显式类型转换。 在Child类的Main()方法中的最后一条语句就是这么做的。记住:派生类是其基类的特例。这个事实告诉我们:可以在派生类中进行数据类型的转换,使其成为基类的一个实例。清单8-2的最后一行实际上执行了Parent类中的 print()方法。 小结 你已经了解了如何创建一个派生类及其基类。你可以对基类成员进行初始化,以及如何对方法进行隐式或者显式的调用。你也了解了派生类是其基类的一个特例。 C#教程第九课:多态性 本节课将介绍C#的多态性,其目的包括: 1.了解什么是多态性 2.如何定义一个虚方法 3.如何重载一个虚方法 4.如何在程序中运用多态性 面向对象程序设计中的另外一个重要概念是多态性。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。 可以把一组对象放到一个数组中,然后调用它们的方法,在这种场合下,多态性作用就体现出来了,这些对象不必是相同类型的对象。当然,如果它们都继承自某个类,你可以把这些派生类,都放到一个数组中。 如果这些对象都有同名方法,就可以调用每个对象的同名方法。本节课将向你介绍如何完成这些事情。 1.清单9-1. 带有虚方法的基类:DrawingObject.cs using System; public class DrawingObject { public virtual void Draw() { Console.WriteLine("I''m just a generic drawing object."); } } 说明 清单9-1 定义了DrawingObject类。这是个可以让其他对象继承的基类。该类有一个名为Draw()的方法。Draw()方法带有一个virtual修饰符,该修饰符表明:该基类的派生类可以重载该方法。DrawingObject类的 Draw()方法完成如下事情:输出语句"I''m just a generic drawing object."到控制台。 2.清单9-2. 带有重载方法的派生类:Line.cs, Circle.cs, and Square.cs using System; public class Line : DrawingObject { public override void Draw() { Console.WriteLine("I''m a Line."); } } public class Circle : DrawingObject { public override void Draw() { Console.WriteLine("I''m a Circle."); } } public class Square : DrawingObject { public override void Draw() { Console.WriteLine("I''m a Square."); } } 说明 清单9-2定义了三个类。这三个类都派生自DrawingObject类。每个类都有一个同名Draw()方法,这些Draw()方法中的每一个都有一个重载修饰符。重载修饰符可让该方法在运行时重载其基类的虚方法,实现这个功能的条件是:通过基类类型的指针变量来引用该类。 3.清单9-3. 实现多态性的程序:DrawDemo.cs using System; public class DrawDemo { public static int Main(string[] args) { DrawingObject[] dObj = new DrawingObject[4]; dObj[0] = new Line(); dObj[1] = new Circle(); dObj[2] = new Square(); dObj[3] = new DrawingObject(); foreach (DrawingObject drawObj in dObj) { drawObj.Draw(); } return 0; } } 说明 清单9-3演示了多态性的实现,该程序使用了在清单 9-1 和清单9-2中定义的类。在DrawDemo类中的Main()方法中,创建了一个数组, 数组元素是DrawingObject 类的对象。该数组名为dObj,是由四个DrawingObject类型的对象组成。 接下来, 初始化dObj数组, 由于Line, Circle和Square类都是DrawingObject类的派生类,所以这些类可以作为dObj数组元素的类型。 如果C#没有这种功能,你得为每个类创建一个数组。继承的性质可以让派生对象当作基类成员一样用,这样就节省了编程工作量。 一旦数组初始化之后,接着是执行foreach循环,寻找数组中的每个元素。在每次循环中, dObj 数组的每个元素(对象)调用其Draw()方法。多态性体现在:在运行时,各自调用每个对象的Draw()方法。尽管dObj 数组中的引用对象类型是DrawingObject,这并不影响派生类重载DrawingObject 类的虚方法Draw()。 在dObj 数组中,通过指向DrawingObject 基类的指针来调用派生类中的重载的Draw()方法。 输出结果是: I''m a Line. I''m a Circle. I''m a Square. I''m just a generic drawing object. 在DrawDemo 程序中,调用了每个派生类的重载的Draw()方法。 最后一行中,执行的是DrawingObject类的虚方法Draw()。这是因为运行到最后,数组的第四个元素是DrawingObject类的对象。 小结 现在对多态性有所了解之后,你可以在派生类中,实现一个重载基类虚方法的方法。虚方法和重载的派生类方法之间的关系就体现出C#的多态性。 C#教程第十课:属性 本节课将介绍C#的属性,其目的包括: 1.理解什么是属性 2.如何实现属性 3.创建一个只读属性 4.创建一个只写属性 属性是C#中独具特色的新功能。通过属性来读写类中的域,这具有一定的保护功能。在其它语言中,这是通过实现特定的getter和setter方法来实现的。C#的属性具有保护功能,可以让你就象访问域一样访问属性。要了解属性的用法,我们先来看看如何用传统的方法对域进行封装。 1.清单 10-1. 传统的访问类的域的例子:Accessors.cs using System; public class PropertyHolder { private int someProperty = 0; public int getSomeProperty() { return someProperty; } public void setSomeProperty(int propValue) { someProperty = propValue; } } public class PropertyTester { public static int Main(string[] args) { PropertyHolder propHold = new PropertyHolder(); propHold.setSomeProperty(5); Console.WriteLine("Property Value: {0}", propHold.getSomeProperty()); return 0; } } 说明 1.清单 10-1 演示了用传统方法访问类的域的例子。 PropertyHolder类有个我们感兴趣的域someProperty, PropertyHolder类带有两个方法:getSomeProperty和setSomeProperty。getSomeProperty方法返回someProperty域的值。SetSomeProperty方法设置域someProperty的值。 2.类PropertyTester使用类PropertyHolder中的方法来获取someProperty域的值。 Main方法中新创建了一个PropertyHolder对象,之后通过使用setSomeProperty方法,调用propHold对象的setSomeProperty方法,设置其值为5。之后,调用Console.WriteLine方法输出属性值。对propHold对象的getSomeProperty的调用,是用来获取属性值的。它输出"Property Value: 5"到控制台。 3.这种传统的访问域的信息的方法是很好的,因为它支持面向对象的封装的概念。 如果在对域someProperty的实现中,域的类型从int 类型变为byte类型,上述的方法仍然适用。现在,如果采用属性的话,其实现会做得更为平滑。 2.清单 10-2. 使用属性访问类的域:Properties.cs using System; public class PropertyHolder { private int someProperty = 0; public int SomeProperty { get { return someProperty; } set { someProperty = value; } } } public class PropertyTester { public static int Main(string[] args) { PropertyHolder propHold = new PropertyHolder(); propHold.SomeProperty = 5; Console.WriteLine("Property Value: {0}", propHold.SomeProperty); return 0; } } 说明 1.清单 10-2 演示了如何创建和使用属性。 PropertyHolder类中有个"SomeProperty" 属性的实现。注意:属性名的首字母必须大写,这是属性名"SomeProperty"和域名"someProperty"的唯一区别。属性有两种访问操作:get和set。Get访问操作返回的是someProperty域的值。Set访问操作是设置someProperty域的值,其值为"value"的内容。Set访问符号后面的"value"是C#中的保留字。通常,在其他场合下使用"value"关键字会出错。。 2.PropertyTester 类使用PropertyHolder类中的SomeProperty属性。 在Main方法的第一行中, 创建了PropertyHolder对象propHold。之后,把propHold对象的 someProperty 域的值设置为5,很简单,就象对域赋值一样,给属性赋值。 3.Console.WriteLine方法输出 propHold对象的someProperty域的值。 这是通过使用propHold对象的SomeProperty属性来完成的。很简单,就象对域赋值一样,赋值给属性。属性可以设置为只读的,这可以通过在属性的实现中只设一个get访问符号来实现。 3.清单 10-3. 只读属性: ReadOnlyProperty.cs using System; public class PropertyHolder { private int someProperty = 0; public PropertyHolder(int propVal) { someProperty = propVal; } public int SomeProperty { get { return someProperty; } } } public class PropertyTester { public static int Main(string[] args) { PropertyHolder propHold = new PropertyHolder(5); Console.WriteLine("Property Value: {0}", propHold.SomeProperty); return 0; } } 说明 1.清单10-3 演示了如何实现只读属性。 PropertyHolder类中,SomeProperty 属性只有一个get访问操作,没有用到set访问操作。PropertyHolder类中还有个接受整型参数的构造函数。 2.在PropertyTester类的Main方法中,创建了新名为propHold的PropertyHolder类的对象。 propHold对象在实例化时,调用了带参数的PropertyHolder构造函数。在本例中,参数值为5,这对propHold 对象的someProperty域的值进行了初始化。 3.因为PropertyHolder 类的SomeProperty属性是只读的,所以没有其他的方法来设置someProperty域的值。 如果你插入了"propHold.SomeProperty = 7"语句到程序清单中,该程序编译将不会通过,因为SomeProperty是只读属性。在Console.WriteLine 方法中使用SomeProperty属性时,程序执行正常。这是因为该方法调用了SomeProperty属性的get访问操作,这是个只读操作。 4.清单 10-4. 只写属性: WriteOnlyProperty.cs using System; public class PropertyHolder { private int someProperty = 0; public int SomeProperty { set { someProperty = value; Console.WriteLine("someProperty is equal to {0}", someProperty); } } } public class PropertyTester { public static int Main(string[] args) { PropertyHolder propHold = new PropertyHolder(); propHold.SomeProperty = 5; return 0; } } 说明 1.清单 10-4 演示了如何创建和使用只写属性。 这一次,在PropertyHolder类中的SomeProperty属性中,去掉了get访问操作,而加上了set访问操作。其功能是输出someProperty域的值。 2.在PropertyTester 类中的Main方法中,用缺省的构造函数对PropertyTester类进行初始化。 之后,使用propHold 对象的SomeProperty属性,设置该域的值为5。这就调用了propHold 对象的set访问操作, 把someProperty 域的值设置为5,最后,把"someProperty is equal to 5"的信息输出到控制台。 小结 现在,你已经了解了什么是属性,以及属性的使用方法,你也了解了使用属性和使用传统的类的方法之间的区别。属性可以是只读的,也可以是只写的,每种场合下的使用方法,你都有所了解。 C#教程第十一课:索引指示器 本节课将介绍C#的索引指示器,其目的包括: 1.了解什么是索引指示器 2.如何实现索引指示器 3.重载索引指示器 4.了解如何实现多参数的索引指示器 索引指示器并不难使用。它们的用法跟数组相同。在一个类内部,你可以按照你的意愿来管理一组数据的集合。这些对象可以是类成员的有限集合,也可以是另外一个数组,或者是一些复杂的数据结构。不考虑类的内部实现,其数据可以通过使用索引指示器来获得。如下是一个例子: 1.清单 11-1. 索引指示器的例子:IntIndexer.cs using System; /// /// A simple indexer example. /// class IntIndexer { private string[] myData; public IntIndexer(int size) { myData = new string[size]; for (int i=0; i < size; i++) { myData[i] = "empty"; } } public string this[int pos] { get { return myData[pos]; } set { myData[pos] = value; } } static void Main(string[] args) { int size = 10; IntIndexer myInd = new IntIndexer(size); myInd[9] = "Some Value"; myInd[3] = "Another Value"; myInd[5] = "Any Value"; Console.WriteLine("\nIndexer Output\n"); for (int i=0; i < size; i++) { Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]); } } } 说明 1.清单 11-1演示了如何实现一个索引指示器, IntIndexer类有个名为myData的字符串数组,该数组是私有成员,因而其外部成员是看不见的。该数组是在构造函数中进行初始化的,该构造函数带有一个整型size参数,用来初始化myData数组,初始化时 把单词"empty"作为每个数组元素的值。 2.IntIndexer类的下一成员是索引指示器(Indexer),由关键字this和方括号[int pos]标识出来。该成员带有一个位置参数pos。正如你已经猜测到,Indexer的实现同属性一样。Indexer有get 和set访问操作,就同属性中的用法一样。索引指示器(indexer)返回一个字符串,在定义索引指示器时,string这个类型名标志着其返回类型为字符串类型。 3.Main()方法完成如下事情:初始化一个新的IntIndexer对象,添加一些值,并且打印出结果。其输出结果如下: Indexer Output myInd[0]: empty myInd[1]: empty myInd[2]: empty myInd[3]: Another Value myInd[4]: empty myInd[5]: Any Value myInd[6]: empty myInd[7]: empty myInd[8]: empty myInd[9]: Some Value 4.在不少程序语言中,通常都是使用整数作为下标来访问作为数组元素的,但C#的索引指示器不仅能够做到这一点,而且还能够更进一步。 定义索引指示器时,可以带有多个参数,每个参数的类型可以不同。添加的参数由逗号隔开,同方法中的的参数表一样。索引指示器的合法的参数类型包括:整型,枚举类型和字符串。另外,索引指示器也可以被重载。在清单 11-2中,我们修改了前面的程序,以便用来重载索引指示器 ,从而可以接受不同类型的参数。 2.清单 11-2. 重载的索引指示器: OvrIndexer.cs using System; /// /// Implements overloaded indexers. /// class OvrIndexer { private string[] myData; private int arrSize; public OvrIndexer(int size) { arrSize = size; myData = new string[size]; for (int i=0; i < size; i++) { myData[i] = "empty"; } } public string this[int pos] { get { return myData[pos]; } set { myData[pos] = value; } } public string this[string data] { get { int count = 0; for (int i=0; i < arrSize; i++) { if (myData[i] == data) { count++; } } return count.ToString(); } set { for (int i=0; i < arrSize; i++) { if (myData[i] == data) { myData[i] = value; } } } } static void Main(string[] args) { int size = 10; OvrIndexer myInd = new OvrIndexer(size); myInd[9] = "Some Value"; myInd[3] = "Another Value"; myInd[5] = "Any Value"; myInd["empty"] = "no value"; Console.WriteLine("\nIndexer Output\n"); for (int i=0; i < size; i++) { Console.WriteLine("myInd[{0}]: {1}", i, myInd[i]); } Console.WriteLine("\nNumber of \"no value\" entries: {0}", myInd["no value"]); } } 说明 1.清单 11-2 演示了如何重载索引指示器。 带有整型参数pos的第一个索引指示器同清单11-1中的一样,但是,该程序中有个带有字符串参数的新的索引指示器。对于这个新的索引指示器来说,其get操作返回的是同参数值data相匹配的成员的个数。 Set操作把数组中同参数值匹配的元素值该变为value值。 2.在清单11-2的Main()方法中,演示了重载的索引指示器,它接受字符串参数。 该重载的索引指示器调用了set操作,通过使用下列命令: myInd["empty"] = "no value"; set操作把"no value"值赋给myInd 类中所有的值为"empty"的成员。 myInd类的每个成员都已经输出之后,就把最后一个数据输出到控制台,该数据统计数组成员值为"no value"的个数。 使用如下命令:myInd["no value"],就可调用get操作。输出结果如下: Indexer Output myInd[0]: no value myInd[1]: no value myInd[2]: no value myInd[3]: Another Value myInd[4]: no value myInd[5]: Any Value myInd[6]: no value myInd[7]: no value myInd[8]: no value myInd[9]: Some Value Number of "no value" entries: 7 3.在清单 11-2中,两个索引指示器共处在同一个类中, 这是可以的,因为它们有不同的特征。 一个索引指示器的特征是通过索引指示器参数表中的参数个数和类型表现出来的。类能够辨别出其特征,并调用相应的索引指示器。带有多个参数的索引指示器可以用如下格式来实现: public object this[int param1, ..., int paramN] { get { // process and return some class data } set { // process and assign some class data } } 小结 现在你已经了解了索引指示器是用来做什么的,以及其用法。如同数组的用法一样,你可以创建索引指示器来访问类的成员。本文也提到了索引指示器的重载和多参数索引指示器。