使用了附加的new修饰符,编译器就知道你重定义了基类的方法,它应该屏蔽基类方法。但是,如果你按以下方式编写: DerivedClass test = new DerivedClass();((BaseClass)test).TestMethod(); 基类方法的实现就被调用了。这种行为不同于改写方法,后者保证大部分派生方法获得调用。 5.3 类属性 有两种途径揭示类的命名属性——通过域成员或者通过属性。前者是作为具有公共访问性的成员变量而被实现的;后者并不直接回应存储位置,只是通过 存取标志(accessors)被访问。 当你想读出或写入属性的值时,存取标志限定了被实现的语句。用于读出属性的值的存取标志记为关键字get,而要修改属性的值的读写符标志记为set。在你对该理论一知半解以前,请看一下清单5.9中的例子,属性SquareFeet被标上了get和set的存取标志。 清单 5.9 实现属性存取标志 1: using System;2: 3: public class House4: {5: private int m_nSqFeet;6: 7: public int SquareFeet8: {9: get { return m_nSqFeet; }10: set { m_nSqFeet = value; }11: }12: }13: 14: class TestApp15: {16: public static void Main()17: {18: House myHouse = new House();19: myHouse.SquareFeet = 250;20: Console.WriteLine(myHouse.SquareFeet);21: }22: } House类有一个命名为SquareFeet的属性,它可以被读和写。实际的值存储在一个可以从类内部访问的变量中——如果你想当作一个域成员重写它,你所要做的就是忽略存取标志而把变量重新定义为:public int SquareFeet; 对于一个如此简单的变量,这样不错。但是,如果你想要隐藏类内部存储结构的细节时,就应该采用存取标志。在这种情况下,set 存取标志给值参数中的属性传递新值。(可以改名,见第10行。) 除了能够隐藏实现细节外,你还可自由地限定各种操作:get和set:允许对属性进行读写访问。get only:只允许读属性的值。set only:只允许写属性的值。 除此之外,你可以获得实现在set标志中有效代码的机会。例如,由于种种原因(或根本没有原因),你就能够拒绝一个新值。最好是没有人告诉你它是一个动态属性——当你第一次请求它后,它会保存下来,故要尽可能地推迟资源分配。 5.4 索引 你想过象访问数组那样使用索引访问类吗 ?使用C#的索引功能,对它的期待便可了结。 语法基本上象这样:属性 修饰符 声明 { 声明内容} 具体的例子为 public string this[int nIndex]{get { ... }set { ... }} 索引返回或按给出的index设置字符串。它没有属性,但使用了public修饰符。声明部分由类型string和this 组成用于表示类的索引。get和set的执行规则和属性的规则相同。(你不能取消其中一个。) 只存在一个差别,那就是:你几乎可以任意定义大括弧中的参数。限制为,必须至少规定一个参数,允许ref 和out 修饰符。 this关键字确保一个解释。索引没有用户定义的名字,this 表示默认接口的索引。如果类实现了多个接口,你可以增加更多个由InterfaceName.this说明的索引。 为了演示一个索引的使用,我创建了一个小型的类,它能够解析一个主机名为IP地址——或一个IP地址列表(以http://www.microsoft.com为例 )。这个列表通过索引可以访问,你可以看一下清单 5.10 的具体实现。 清单 5.10 通过一个索引获取一个IP地址 1: using System;2: using System.Net;3: 4: class ResolveDNS5: {6: IPAddress[] m_arrIPs;7: 8: public void Resolve(string strHost)9: {10: IPHostEntry iphe = DNS.GetHostByName(strHost);11: m_arrIPs = iphe.AddressList;12: }13: 14: public IPAddress this[int nIndex]15: {16: get17: {18: return m_arrIPs[nIndex];19: }20: }21: 22: public int Count23: {24: get { return m_arrIPs.Length; }25: }26: }27: 28: class DNSResolverApp29: {30: public static void Main()31: {32: ResolveDNS myDNSResolver = new ResolveDNS();33: myDNSResolver.Resolve("http://www.microsoft.com");34: 35: int nCount = myDNSResolver.Count;36: Console.WriteLine("Found {0} IP's for hostname", nCount);37: for (int i=0; i < nCount; i++)38: Console.WriteLine(myDNSResolver);39: } 40: } 为了解析主机名,我用到了DNS类,它是System .Net 名字空间的一部分。但是,由于这个名字空间并不包含在核心库中,所以必须在编译命令行中引用该库:csc /r:System.Net.dll /out:resolver.exe dnsresolve.cs 解析代码是向前解析的。在该 Resolve方法中,代码调用DNS类的静态方法GetHostByName,它返回一个IPHostEntry对象。结果,该对象包含有我要找的数组——AddressList数组。在退出Resolve 方法之前,在局部的对象实例成员m_arrIPs中,存储了一个AddressList array的拷贝(类型IPAddress 的对象存储在其中)。 具有现在生成的数组 ,通过使用在类ResolveDNS中求得的索引,应用程序代码就可以在第37至38行列举出IP地址。(在第6章 "控制语句",有更多有关语句的信息。) 因为没有办法更改IP地址,所以仅给索引使用了get存取标志。为了简单其见,我忽略了数组的边界溢出检查。 5.4 事件 当你写一个类时,有时有必要让类的客户知道一些已经发生的事件。如果你是一个具有多年编程经验的程序员,似乎有很多的解决办法,包括用于回调的函数指针和用于ActiveX控件的事件接收(event sinks)。现在你将要学到另外一种把客户代码关联到类通知的办法——使用事件。 事件既可以被声明为类域成员(成员变量),也可以被声明为属性。两者的共性为,事件的类型必定是代表元,而函数指针原形和C#的代表元具有相同的含义。 每一个事件都可以被0或更多的客户占用,且客户可以随时关联或取消事件。你可以以静态或者以实例方法定义代表元,而后者很受C++程序员的欢迎。 既然我已经提到了事件的所有功能及相应的代表元,请看清单5.11中的例子。它生动地体现了该理论。 清单5.11 在类中实现事件处理 1: using System;2: 3: // 向前声明4: public delegate void EventHandler(string strText);5: 6: class EventSource7: {8: public event EventHandler TextOut;9: 10: public void TriggerEvent()11: {12: if (null != TextOut) TextOut("Event triggered");13: }14: }15: 16: class TestApp17: {18: public static void Main()19: {20: EventSource evsrc = new EventSource();21: 22: evsrc.TextOut += new EventHandler(CatchEvent);23: evsrc.TriggerEvent();24: 25: evsrc.TextOut -= new EventHandler(CatchEvent);26: evsrc.TriggerEvent();27: 28: TestApp theApp = new TestApp();29: evsrc.TextOut += new EventHandler(theApp.InstanceCatch);30: evsrc.TriggerEvent();31: }32: 33: public static void CatchEvent(string strText)34: {35: Console.WriteLine(strText);36: }37: 38: public void InstanceCatch(string strText)39: {40: Console.WriteLine("Instance " + strText);41: }42: } 第4行声明了代表元(事件方法原形),它用来给第8行中的EventSource类声明TextOut事件域成员。你可以观察到代表元作为一种新的类型声明,当声明事件时可以使用代表元。

评论