




已阅读5页,还剩13页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
深入浅出.NET中关键字-base和this虽然访问关键字不是很难理解的话题,我们还是有可以深入讨论的地方来理清思路。还是老办法,我的问题先列出来,您是否做好了准备。 1.是否可以在静态方法中使用base和this,为什么? 2.base常用于哪些方面?this常用于哪些方面? 3.可以base访问基类的一切成员吗? 4.如果有三层或者更多继承,那么最下级派生类的base指向那一层呢?例如.NET体系中,如果以base访问,则应该是直接父类实例呢,还是最高层类实例呢? 5.以base和this应用于构造函数时,继承类对象实例化的执行顺序如何? 2. 基本概念base和this在C#中被归于访问关键字,顾名思义,就是用于实现继承机制的访问操作,来满足对对象成员的访问,从而为多态机制提供更加灵活的处理方式。 2.1 base关键字其用于在派生类中实现对基类公有或者受保护成员的访问,但是只局限在构造函数、实例方法和实例属性访问器中,MSDN中小结的具体功能包括:调用基类上已被其他方法重写的方法。 指定创建派生类实例时应调用的基类构造函数。 2.2 this关键字其用于引用类的当前实例,也包括继承而来的方法,通常可以隐藏this,MSDN中的小结功能主要包括:限定被相似的名称隐藏的成员 将对象作为参数传递到其他方法 声明索引器 3. 深入浅出3.1 示例为上下面以一个小示例来综合的说明,base和this在访问操作中的应用,从而对其有个概要了解,更详细的规则和深入我们接着阐述。本示例没有完全的设计概念,主要用来阐述base和this关键字的使用要点和难点阐述,具体的如下: using System;namespace A.My_Must_net public class Action public static void ToRun(Vehicle vehicle) Console.WriteLine(0 is running., vehicle.ToString(); public class Vehicle private string name; private int speed; private string array = new string10; public Vehicle() /限定被相似的名称隐藏的成员 public Vehicle(string name, int speed) = name; this.speed = speed; public virtual void ShowResult() Console.WriteLine(The top speed of 0 is 1., name, speed); public void Run() /传递当前实例参数 Action.ToRun(this); /声明索引器,必须为this,这样就可以像数组一样来索引对象 public string thisint param getreturn arrayparam; setarrayparam = value; public class Car: Vehicle /派生类和基类通信,以base实现,基类首先被调用 /指定创建派生类实例时应调用的基类构造函数 public Car() : base(Car, 200) public Car(string name, int speed) : this() public override void ShowResult() /调用基类上已被其他方法重写的方法 base.ShowResult(); Console.WriteLine(Its a cars result.); public class Audi : Car public Audi() : base(Audi, 300) public Audi(string name, int speed) : this() public override void ShowResult() /由三层继承可以看出,base只能继承其直接基类成员 base.ShowResult(); base.Run(); Console.WriteLine(Its audis result.); public class BaseThisTester public static void Main(string args) Audi audi = new Audi(); audi1 = A6; audi2 = A8; Console.WriteLine(audi1); audi.Run(); audi.ShowResult(); 3.2 示例说明上面的示例基本包括了base和this使用的所有基本功能演示,具体的说明可以从注释中得到解释,下面的说明是对注释的进一步阐述和补充,来说明在应用方面的几个要点:1.base常用于,在派生类对象初始化时和基类进行通信。 2.base可以访问基类的公有成员和受保护成员,私有成员是不可访问的。 3.this指代类对象本身,用于访问本类的所有常量、字段、属性和方法成员,而且不管访问元素是任何访问级别。因为,this仅仅局限于对象内部,对象外部是无法看到的,这就是this的基本思想。另外,静态成员不是对象的一部分,因此不能在静态方法中引用this。 4.在多层继承中,base可以指向的父类的方法有两种情况:一是有重载存在的情况下,base将指向直接继承的父类成员的方法,例如Audi类中的ShowResult方法中,使用base访问的将是Car.ShowResult()方法,而不能访问Vehicle.ShowResult()方法;而是没有重载存在的情况下,base可以指向任何上级父类的公有或者受保护方法,例如Audi类中,可以使用base访问基类Vehicle.Run()方法。这些我们可以使用ILDasm.exe,从IL代码中得到答案。 .method public hidebysig virtual instance void ShowResult() cil managed / 代码大小 27 (0x1b) .maxstack 8 IL_0000: nop IL_0001: ldarg.0 /base调用父类成员 IL_0002: call instance void A.My_Must_net.Car:ShowResult() IL_0007: nop IL_0008: ldarg.0 /base调用父类成员,因为没有实现Car.Run(),所以指向更高级父类 IL_0009: call instance void A.My_Must_net.Vehicle:Run() IL_000e: nop IL_000f: ldstr Its audis result. IL_0014: call void mscorlibSystem.Console:WriteLine(string) IL_0019: nop IL_001a: ret / end of method Audi:ShowResult3.3 深入剖析 如果有三次或者更多继承,那么最下级派生类的base指向那一层呢?例如.NET体系中,如果以base访问,则应该是直接父类实例呢,还是最高层类实例呢?首先我们有必要了解类创建过程中的实例化顺序,才能进一步了解base机制的详细执行过程。一般来说,实例化过程首先要先实例化其基类,并且依此类推,一直到实例化System.Object为止。因此,类实例化,总是从调用System.Object.Object()开始。因此示例中的类Audi的实例化过程大概可以小结为以下顺序执行,详细可以参考示例代码分析。 1.执行System.Object.Object(); 2.执行Vehicle.Vehicle(string name, int speed); 3.执行Car.Car(); 4.执行Car.Car(string name, int speed); 5.执行Audi.Audi(); 6.执行Audi.Audi(string name, int speed)。 我们在充分了解其实例化顺序的基础上就可以顺利的把握base和this在作用于构造函数时的执行情况,并进一步了解其基本功能细节。下面更重要的分析则是,以ILDASM.exe工具为基础来分析IL反编译代码,以便更深层次的了解执行在base和this背后的应用实质,只有这样我们才能说对技术有了基本的剖析。Main方法的执行情况为:.method public hidebysig static void Main(string args) cil managed .entrypoint / 代码大小 61 (0x3d) .maxstack 3 .locals init (class A.My_Must_net.Audi V_0) IL_0000: nop /使用newobj指令创建新的对象,并调用构造函数初始化 IL_0001: newobj instance void A.My_Must_net.Audi:.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.1 IL_0009: ldstr A6 IL_000e: callvirt instance void A.My_Must_net.Vehicle:set_Item(int32, string) IL_0013: nop IL_0014: ldloc.0 IL_0015: ldc.i4.2 IL_0016: ldstr A8 IL_001b: callvirt instance void A.My_Must_net.Vehicle:set_Item(int32, string) IL_0020: nop IL_0021: ldloc.0 IL_0022: ldc.i4.1 IL_0023: callvirt instance string A.My_Must_net.Vehicle:get_Item(int32) IL_0028: call void mscorlibSystem.Console:WriteLine(string) IL_002d: nop IL_002e: ldloc.0 IL_002f: callvirt instance void A.My_Must_net.Vehicle:Run() IL_0034: nop IL_0035: ldloc.0 /base.ShowResult最终调用的是最高级父类Vehicle的方法, /而不是直接父类Car.ShowResult()方法,这是应该关注的 IL_0036: callvirt instance void A.My_Must_net.Vehicle:ShowResult() IL_003b: nop IL_003c: ret / end of method BaseThisTester:Main因此,对重写父类方法,最终指向了最高级父类的方法成员。4. 通用规则尽量少用或者不用base和this。除了决议子类的名称冲突和在一个构造函数中调用其他的构造函数之外,base和this的使用容易引起不必要的结果。 在静态成员中使用base和this都是不允许的。原因是,base和this访问的都是类的实例,也就是对象,而静态成员只能由类来访问,不能由对象来访问。 base是为了实现多态而设计的。 使用this或base关键字只能指定一个构造函数,也就是说不可同时将this和base作用在一个构造函数上。 简单的来说,base用于在派生类中访问重写的基类成员;而this用于访问本类的成员,当然也包括继承而来公有和保护成员。 除了base,访问基类成员的另外一种方式是:显示的类型转换来实现。只是该方法不能为静态方法。 5. 结论base和this关键字,不是特别难于理解的内容,本文之所以将其作为系列的主题,除了对其应用规则做以小结之外,更重要的是在关注其执行细节的基础上,对语言背景建立更清晰的把握和分析,这些才是学习和技术应用的根本所在,也是.NET技术框架中本质诉求。对学习者来说,只有从本质上来把握概念,才能在变化非凡的应用中,一眼找到答案。 深入浅出关键字-把new说透2007年06月12日 星期二 09:39你真的理解了new吗?如果是,那请不要浪费时间,如果不是,那请继续本文的循序之旅。 下面几个 问题可以大概的考察你对new的掌握,开篇之前,希望大家做个检验,如果通过了,直接关掉本页即可。如果没有通过,希望本文的阐述能帮你找出答案。new一个class对象和new一个struct或者enum有什么不同? new在.NET中有几个用途,除了创建对象实例,还能做什么? new运算符,可以重载吗? 范型中,new有什么作用? new一个继承下来的方法和override一个继承方法有何区别? int i和int i = new int()有什么不同? 2. 基本概念一般说来,new关键字在.NET中用于以下几个场合,这是MSDN的典型解释:作为运算符, 用于创建对象和调用构造函数。 本文的重点内容,本文在下一节来重点考虑。作为修饰符,用于向基类成员隐藏继承成员。 作为修饰符,基本的规则可以总结为:实现派生类中隐藏方法,则基类方法必须定义为virtual;new作为修饰符,实现隐藏基类成员时,不可和override共存,原因是这两者语义相斥:new用于实现创建一个新成员,同时隐藏基类的同名成员;而override用于实现对基类成员的扩展。另外,如果在子类中隐藏了基类的数据成员,那么对基类原数据成员的访问,可以通过base修饰符来完成。例如: new作为修饰符using System;namespace A.My_Must_net class Number public static int i = 123; public void ShowInfo() Console.WriteLine(base class-); public virtual void ShowNumber() Console.WriteLine(i.ToString(); class IntNumber : Number new public static int i = 456; public new virtual void ShowInfo() Console.WriteLine(Derived class-); public override void ShowNumber() Console.WriteLine(Base number is 0, Number.i.ToString(); Console.WriteLine(New number is 0, i.ToString(); class Tester public static void Main(string args) Number num = new Number(); num.ShowNumber(); IntNumber intNum = new IntNumber(); intNum.ShowNumber(); Number number = new IntNumber(); /究竟调用了谁? number.ShowInfo(); /究竟调用了谁? number.ShowNumber(); 作为约束,用于在泛型声明中约束可能用作类型参数的参数的类型。 MSDN中的定义是:new 约束指定泛型类声明中的任何类型参数都必须有公共的无参数构造函数。当泛型类创建类型的新实例时,将此约束应用于类型参数。注意:new作为约束和其他约束共存时,必须在最后指定。其定义方式为: class Genericer where T : new() public T GetItem() return new T(); 实现方式为: class MyCls private string _name; public MyCls() _name = Emma; class MyGenericTester public static void Main(string args) Genericer MyGen = new Genericer(); Console.WriteLine(MyGen.GetItem().Name); 使用new实现多态。 这不是我熟悉的话题,详细的内容可以参见 多态与 new C#,这里有较详细的论述。 3. 深入浅出作为修饰符和约束的情况,不是很难理解的话题,正如我们看到本文开篇提出的问题,也大多集中在new作为运算符的情况,因此我们研究的重点就是揭开new作为运算符的前世今生。Jeffrey Richter在其著作中,极力推荐读者使用ILDASM工具查看IL语言细节,从而提高对.NET的深入探究,在我认为这真是一条不错的建议,也给了自己很多提高的空间挖掘。因此,以下是本人的一点建议,我将在后续的系列中,关于学习方法论的讨论中深入探讨,这里只是顺便小议,希望有益于大家。1 不断的学习代码;2 经常看看IL语言的运行细节,对于提供.NET的认识非常有效。 文归正题,new运算符用于返回一个引用,指向系统分配的托管堆的内存地址。因此,在此我们以Reflector工具,来了解以下new操作符执行的背后,隐藏着什么玄机。 首先我们实现一段最简单的代码,然后分析其元数据的实现细节,来探求new在创建对象时到做了什么? new作为运算符using System;namespace A.My_Must_net class MyClass private int _id; public MyClass(int id) _id = id; struct MyStruct private string _name; public MyStruct(string name) _name = name; class NewReflecting public static void Main(string args) int i; int j = new int(); MyClass mClass = new MyClass(123); MyStruct mStruct = new MyStruct(My Struct); 使用Reflector工具反编译产生的IL代码如下为: IL元数据分析.method public hidebysig static void Main(string args) cil managed .entrypoint .maxstack 2 .locals init ( 0 int32 num, 1 int32 num2, 2 class A.My_Must_net._05_new.MyClass class2, 3 valuetype A.My_Must_net._05_new.MyStruct struct2) L_0000: nop /初始化j为0 L_0001: ldc.i4.0 L_0002: stloc.1 /使用newobj指令创建新的对象,并调用构造函数以0x76(123的16进制)初始化 L_0003: ldc.i4.s 0x7b L_0005: newobj instance void A.My_Must_net._05_new.MyClass:.ctor(int32) L_000a: stloc.2 /加载“My Struct” L_000b: ldloca.s struct2 L_000d: ldstr My Struct /调用构造函数执行初始化 L_0012: call instance void A.My_Must_net._05_new.MyStruct:.ctor(string) L_0017: nop L_0018: ret 从而可以得出以下结论: new一个class时,new完成了以下两个方面的内容:一是调用newobj命令来为实例在托管堆中分配内存;二是调用构造函数来实现对象初始化。 new一个struct时,new运算符用于调用其带构造函数,完成实例的初始化。 new一个int时,new运算符用于初始化其值为0。 另外必须清楚,值类型和引用类型在分配内存时是不同的,值类型分配于线程的堆栈(stack)上,并变量本身就保存其实值,因此也不受GC的控制,;而引用类型变量,包含了指向托管堆的引用,内存分配于托管堆(managed heap)上,内存收集由GC完成。 另外还有以下规则要多加注意: new运算符不可重载。 new分配内存失败,将引发OutOfMemoryException异常。 对于基本类型来说,使用new操作符来进行初始化的好处是,某些构造函数可以完成更优越的初始化操作,而避免了不高明的选择,例如:string str = new string(*, 100);string str = new string(new char a, b, c);而不是string str = *; 4. 结论 我能说的就这么多了,至于透了没透,作者的能量也就这么多了。希望园子的大牛们常来扔块砖头,对我也是一种莫大的促进。但是作为基本的原理和应用,我想对大部分的需求是满足了。希望这种力求深入浅出的介绍,能给你分享new关键字和其本质的来龙去脉能有所帮助。接口的无敌解释2007年06月13日 星期三 21:32接口简单的说接口就是一个契约或者规范.比如遥控器,国家出台了一个国家遥控器规范,明文要求所有的遥控器厂家都要遵循这个规范,如果不遵循规范就不给3C认证标志,就不允许上市出卖.为什么要这个规范呢?大家在时间生活中会经常碰到,甲厂的遥控器不能遥控乙厂的电视,电视遥控器不能遥控其它电器如空调,冰箱.!原因是什么呢?是各个遥控器都没有遵循一个规范,电波有长有短,电压有高有低,导致各自为政,4分5列! 可以想像出国家遥控器标准只是是规定遥控器的一些重要技术指标,比如要发射波应该多长,电压应该多高,.,但它绝对不会规范出遥控器的材质,形状,重量和颜色,也是说规范把所有同遥控无关的东西都抛弃了!每个遥控器厂家只要遵循了规范,那么对遥控器可以有任意的诠释.比如A厂可以用铁做,牢固无比,B厂可以用纸,可以任意折叠,anyway,不管用什么做,做出什么样子,只要遵循规范的遥控器就可以遥控所有的电器(当然电器厂家也要遵循一定的规范),甚至可以遥控导弹发射!利害吧,这就是接口的威力. 再详细点,接口就是一个规范,他和具体的实现无关!接口是规范(虚的),他只是一张纸,也是说在实际的使用中接口只有依托一个实现了它的类的实例,才会有意义,如上面的各个厂家做的遥控器产品.每个实现接口的类(厂家)必需实现接口中所有的功能. 一旦一个类实现了一个接口,就可说一个类和接口捆绑了(这个很重要,做题目的时候会用到) 来个例子 interface 遥控器规范 /国家定义的遥控器规范 ,每个遥控器厂家必需实现(诠释)它 int 波长(); int 电压(); class 甲厂铁遥控器 : 遥控器规范 /甲厂的遥控器实现(诠释)了这个规范,它和遥控器规范捆绑了!好,它可以在市场上出售了 public int 波长(); /规范上定义的指标 public int 电压(); /规范上定义的指标 public int 形状() 正方形; /甲厂自己对该产品的诠释 public int 材质() ( 铁 ; /甲厂自己对该产品的诠释 class 乙厂纸遥控器 : 遥控器规范 /甲厂的遥控器实现(诠释)了这个规范,它和遥控器规范捆绑了!好,它可以在市场上出售了 public int 波长(); /规范上定义的指标 public int 电压(); /规范上定义的指标 public int 形状()( 圆形); /甲厂自己对该产品的诠释,是圆形 public int 材质()( 纸); /甲厂自己对该产品的诠释,用纸做,好酷! class 电器 procedure 接收遥控(遥控器规范 ) /电器上,接收遥控指令 . 接收(遥控器规范.波长) ; 接收(遥控器规范.电压); . static main() 甲厂铁遥控器 ControlA ; /申明控制器对象 乙厂纸遥控器 ControlB ; ControlA = new 甲厂铁遥控器(); /实例化控制器对象,这个时候系统在托管堆中为该对象分配了空间 ControlB = new 乙厂纸遥控器() ; 遥控器规范 ControlInterfaceA = (遥控器规范)遥控器1 ; /把对象实例转换成一个规范,为什么呢?因为我家的电器.只能识别遥控器规范,它识别不到具体的遥控器 遥控器规范 ControlInterfaceB = (遥控器规范)遥控器2; /同上 电器 我家的电器 = new 电器(); 我家的电器.接收遥控(ControlInterfaceA) /我用甲厂遥控器遥控我家的电器. 注意: 这里的ControlInterfaceA是不能单独存在的,它必要依赖实现了遥控器规范的类的实例ControlA.道理很简单,接口是一个指针,不会被分配空间,你就无法使用,只有和一个具体类的实例联系了,才有了可以活跃空间. 我家的电器.接收遥控(ControlInterfaceB) /我用乙厂遥控器遥控我家的电器 . /下面是我的的想像,我可以用遥控器来控制导弹发射! 我的导弹.接收遥控(ControlInterfaceA); 我的导弹.接收遥控(ControlInterfaceB); . -接口的执行好了,有了接口的概念,再来谈c#程序在运行中是如何使用接口的,如何访问接口函数.具体流程如下 a.当调用一个接口的函数时,系统会去检查这个接口对应实例是什么? b.找到这个实例后,再去找这个实例对应的实例类是什么(什么是实例类,参看读书笔记二) c.根据这个实例类去检查该实例类是否和接口发生了捆绑(看是否实现了该接口,冒号后面就是) d.好!如果实例类实现了该接口(发生了捆绑) ,它就在这个实例类中找函数的定义.然后执行该函数.执行结束. e.如果没找到,他就继续往父类找,直到找到第一个和接口捆绑的父类为止 f.找到后,它再检查该函数是否是虚拟函数, g.如果不是,他马上就执行它 . h 如果是,麻烦了,系统又要从头来过,去检查该实例类的函数是否重载了该函数,.具体过程见(c#读书笔记2). 例子: Interface I void Func() ; Class A : I public virtual void Func() Console.WriteLine(FuncA); class B : A , I /注意这里的意思? public void Func() Console.WriteLine(FuncB); class C : A public override void Func() Console.WriteLine(FuncC); static main() I a = new A() ; /申明了接口a,并马上和一个类的实例发生关系了 I b = new B() ; /申明了接口b,并马上和一个类的实例发生关系了 I c = new C() ; /申明了接口c,并马上和一个类的实例发生关系了 a.Func() ; /检查a的实例A, 发现A和接口I捆绑了,所以执行A的函数Func ,结果: FuncA b.Func() ; /检查b的实例B, 发现B和接口I捆绑了,所以执行B的函数Func ,结果: FuncB c.Func() ; /家常c的实例C,发现其没有和接口I捆绑,系统继续找它的父类. 发现A和I捆绑了,他就去找函数A,发现A是虚拟函数,系统又从头来找类的实例C,发现C重载(override)了Func,好了,马上执行该函数. 结果是FuncC; c#学习体会:使用 ref 和 out 传递数组2007年07月10日 星期二 22:49c#学习体会:使用 ref 和 out 传递数组(downmoon),希望与大家分享1、与所有的 out 参数一样,在使用数组类型的 out 参数前必须先为其赋值,即必须由接受方为其赋值。例如:public static void MyMethod(out int arr). arr = new int10; / 数组arr的明确委派2、与所有的 ref 参数一样,数组类型的 ref 参数必须由调用方明确赋值。因此不需要由接受方明确赋值。可以将数组类型的 ref 参数更改为调用的结果。例如,可以为数组赋以 null 值,或将其初始化为另一个数组。例如:public static void MyMethod(ref int arr). arr = new int10; / arr初始化为一个新的数组下面的两个示例说明 out 和 ref 在将数组传递给方法上的用法差异。示例 1在此例中,在调用方(Main 方法)中声明数组 myArray,并在 FillArray 方法中初始化此数组。然后将数组元素返回调用方并显示。using System;class TestOut. static public void FillArray(out int myArray) . / 初始化数组(必须): myArray = new int5 .1, 2, 3, 4, 5; static public void Main() . int myArray; / 初始化数组(不是必须的!) / 传递数组给(使用out方式的)调用方: FillArray(out myArray); / 显示数组元素 Console.WriteLine(数组元素是:); for (int i=0; i myArr
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 回炉培训考试题库及答案
- 2025年焊工作业人员技能知识练习题含答案
- 2025年焊工复审证试题及答案
- 特种作业管理知识培训课件
- 2025年海南省三沙市事业单位工勤技能考试题库及答案
- 消费机器人组装质量检验工艺考核试卷及答案
- 漆器金银漆嵌石工艺考核试卷及答案
- 特殊食品培训知识点总结课件
- 如何与不同性格的人建立良好关系
- 特殊的汽车课件
- 医疗器械生产企业GMP培训专家讲座
- 2023年中远海运船员管理有限公司招聘笔试题库及答案解析
- 辐射及其安全防护(共38张PPT)
- 金风15兆瓦机组变流部分培训课件
- 膀胱镜检查记录
- 沈阳终止解除劳动合同证明书(三联)
- 化工装置静设备基本知识
- 电脑节能环保证书
- 美国共同基金SmartBeta布局及借鉴
- 露天矿山危险源辨识汇总
- 国家城镇救援队伍能力建设与分级测评指南
评论
0/150
提交评论