new、vitual、override之间那点事

来源:互联网 发布:知己知彼软件怎么样 编辑:程序博客网 时间:2024/06/06 05:14

转载From : http://blog.csdn.net/lastBeachhead/archive/2008/11/29/3402257.aspx

 

 1)第一种情况:子类某个方法使用new修饰,但父类中并没有该方法。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }
  14.     public class Son : Father
  15.     {
  16.         new public void SonSay() 
  17.         {
  18.             Console.WriteLine("Son.SonSay()");
  19.         }
  20.     }
  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father father = new Father();
  26.             Son son = new Son();
  27.             father.Say();
  28.             son.SonSay();
  29.             Console.ReadLine();
  30.         }
  31.     }
  32. }

    Son类继承自Father,Father类中并没有SonSay()这个方法,在Son类中定义SonSay()方法,并且定义为new的,结果会怎么样呢?
    运行结果:
    Father.Say()
    Son.SonSay()
    即运行结果没问题,但编译器会提出一个警告:子类Son中的方法SonSay()在父类中并不存在,没必要使用new关键字。
    2)第二种情况:父类中有一个方法,子类中也有一个同样签名的方法,但父类中该方法没有使用使用virtual、override、abstract修饰,子类中该方法也没有使用new或者override修饰。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }
  14.     public class Son : Father
  15.     {
  16.          public void Say() 
  17.         {
  18.             Console.WriteLine("Son.Say()");
  19.         }
  20.     }
  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father[] persons = new Father[2];
  26.             persons[0] = new Father();
  27.             persons[1] = new Son();
  28.             persons[0].Say();
  29.             persons[1].Say();
  30.             Console.ReadLine();
  31.         }
  32.     }
  33. }

    运行结果:
    Father.Say()
    Father.Say()
    为什么persons[1].Say()的运行结果也是Father.Say()呢?person[1]的实例类型明明是Son啊。因为在c#中,如果子类中的方法签名如果和父类中的方法签名一致而不是override的(名字相同,参数格式和类型也一致,跟返回值没关),那么就自动隐式的标记为new的方法了。而new出来的方法是不支持运行时绑定的,它是编译时绑定,即跟声明时类型有关,而跟运行时该对象的实际类型无关,即编译时已经确定是调用父类的还是子类的方法了。
另外编译器会给出一个警告:该方法签名跟父类的一样,如果是有意new的话,请加上new关键字。
    3)第三种情况:子类的某个方法直接标识为override的,而父类该方法并没有声明为vitual、abstract或者override的。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }
  14.     public class Son : Father
  15.     {
  16.          public override void Say() 
  17.         {
  18.             Console.WriteLine("Son.Say()");
  19.         }
  20.     }
  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father[] persons = new Father[2];
  26.             persons[0] = new Father();
  27.             persons[1] = new Son();
  28.             persons[0].Say();
  29.             persons[1].Say();
  30.             Console.ReadLine();
  31.         }
  32.     }
  33. }
    这次运行结果会怎么样呢?编译器这次直接报错了,而不警告:
'NewVituslOverride.Son.Say()': cannot override inherited member 'NewVituslOverride.Father.Say()' because it is not marked virtual, abstract, or override。
    编译错误内容很清楚,我就不解释了。
    本例子说明override不是随便用的,必须父类的方法、属性、索引、事件是abstract、vitual或者override的时候,子类才能override它。
    4)第四种情况:子类new父类中的方法。
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }
  14.     public class Son : Father
  15.     {
  16.          public new void Say() 
  17.         {
  18.             Console.WriteLine("Son.Say()");
  19.         }
  20.     }
  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father[] persons = new Father[2];
  26.             persons[0] = new Father();
  27.             persons[1] = new Son();
  28.             persons[0].Say();
  29.             persons[1].Say();
  30.             Console.ReadLine();
  31.         }
  32.     }
  33. }
    运行结果:
    Father.Say()
    Father.Say()
    如果第二种情况看懂了,就知道这个例子的运行结果和第二种情况一样的。只是这次代码用new把意思说的明明白白,子类我就是要隐藏父类的方法Say()。由于person[1]的声明类型是Father(虽然实际类型是Son),所以还是因为编译时绑定的原因,person[1].Say()跑出来的结果还是Father.Say()。
    5)父类方法使用vitual,子类使用override重写

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. namespace NewVituslOverride
  6. {
  7.     public class Father 
  8.     {
  9.         public virtual void Say() 
  10.         {
  11.             Console.WriteLine("Father.Say()");
  12.         }
  13.     }
  14.     public class Son : Father
  15.     {
  16.          public override void Say() 
  17.         {
  18.             Console.WriteLine("Son.Say()");
  19.         }
  20.     }
  21.     class Program
  22.     {
  23.         static void Main(string[] args)
  24.         {
  25.             Father[] persons = new Father[2];
  26.             persons[0] = new Father();
  27.             persons[1] = new Son();
  28.             persons[0].Say();
  29.             persons[1].Say();
  30.             Console.ReadLine();
  31.         }
  32.     }
  33. }

    运行结果:
    Father.Say()
    Son.Say()
    这次虽然persons[1]的声明类型是Father,但因为Say()是vitual的,需要运行时根据对象的实际类型来调用,因为运行时person[1]的实际类型是Son,所以运行的是Son的Say()。
我们来仔细看下这个例子生成的IL代码:
    Father类:

  1. .class public auto ansi beforefieldinit Father
  2.     extends [mscorlib]System.Object
  3. {
  4.     .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  5.     {
  6.         .maxstack 8
  7.         L_0000: ldarg.0 
  8.         L_0001: call instance void [mscorlib]System.Object::.ctor()
  9.         L_0006: ret 
  10.     }
  11.     .method public hidebysig newslot virtual instance void Say() cil managed
  12.     {
  13.         .maxstack 8
  14.         L_0000: nop 
  15.         L_0001: ldstr "Father.Say()"
  16.         L_0006: call void [mscorlib]System.Console::WriteLine(string)
  17.         L_000b: nop 
  18.         L_000c: ret 
  19.     }
  20. }

    从里面可以看出Father类的Say()方法,被标识为newslot virtual的了。
    我们接着来看Son类的IL代码:

  1. .class public auto ansi beforefieldinit Son
  2.     extends NewVituslOverride.Father
  3. {
  4.     .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  5.     {
  6.         .maxstack 8
  7.         L_0000: ldarg.0 
  8.         L_0001: call instance void NewVituslOverride.Father::.ctor()
  9.         L_0006: ret 
  10.     }
  11.     .method public hidebysig virtual instance void Say() cil managed
  12.     {
  13.         .maxstack 8
  14.         L_0000: nop 
  15.         L_0001: ldstr "Son.Say()"
  16.         L_0006: call void [mscorlib]System.Console::WriteLine(string)
  17.         L_000b: nop 
  18.         L_000c: ret 
  19.     }
  20. }

    从Son的IL代码中我们可以看出来,虽然我们在c#代码中声明Son类的Say()方法为override的,但实际上转换为IL代码后,该方法在Son类中是virtual的。
    接着我们来看下Program类的IL代码:

  1. .class private auto ansi beforefieldinit Program
  2.     extends [mscorlib]System.Object
  3. {
  4.     .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
  5.     {
  6.         .maxstack 8
  7.         L_0000: ldarg.0 
  8.         L_0001: call instance void [mscorlib]System.Object::.ctor()
  9.         L_0006: ret 
  10.     }
  11.     .method private hidebysig static void Main(string[] args) cil managed
  12.     {
  13.         .entrypoint
  14.         .maxstack 3
  15.         .locals init (
  16.             [0] class NewVituslOverride.Father[] persons)
  17.         L_0000: nop 
  18.         L_0001: ldc.i4.2 
  19.         L_0002: newarr NewVituslOverride.Father
  20.         L_0007: stloc.0 
  21.         L_0008: ldloc.0 
  22.         L_0009: ldc.i4.0 
  23.         L_000a: newobj instance void NewVituslOverride.Father::.ctor()
  24.         L_000f: stelem.ref 
  25.         L_0010: ldloc.0 
  26.         L_0011: ldc.i4.1 
  27.         L_0012: newobj instance void NewVituslOverride.Son::.ctor()
  28.         L_0017: stelem.ref 
  29.         L_0018: ldloc.0 
  30.         L_0019: ldc.i4.0 
  31.         L_001a: ldelem.ref 
  32.         L_001b: callvirt instance void NewVituslOverride.Father::Say()
  33.         L_0020: nop 
  34.         L_0021: ldloc.0 
  35.         L_0022: ldc.i4.1 
  36.         L_0023: ldelem.ref 
  37.         L_0024: callvirt instance void NewVituslOverride.Father::Say()
  38.         L_0029: nop 
  39.         L_002a: call string [mscorlib]System.Console::ReadLine()
  40.         L_002f: pop 
  41.         L_0030: ret 
  42.     }
  43. }

    构造函数就不用看了,主要是看Main函数及其里面的执行顺序。
    在Main函数中,先声明了一个Father[]数组,接着设置该数组的长度为2,接着创建了一个Father对象和一个Son对象。
    关键是这两个对象调用Say()方法。我们可以看到是使用callvirl命令的,而不是call命令,使用callvirl命令就要求运行时检查对象的实际类型,然后调用该类型的对应方法。