C# - new和override的差异与目的

今天在公司,同事在学习C#,刚好学到了继承这一章节,但是对于new和override并不是很了解,所以就跑来问小弟我,好死不死,刚好问到这个几百年都没什么在用的东西,所以紧急恶补一下后,决定还是把new和override写到Blog来好了,以防未来又被问倒…( 至少未来被问到,又忘记的时候可以说,我Blog里面有写XDD )。

override

当然,这部分不是这篇文章的重点,相信学过Java或是常写C#等等OO语言的人,对这个东西应该一点也不陌生了,我们就直接来看看范例吧;下面这个范例其实很简单,简单的说,就是Class2继承Class1,也就是说父类别为Class1,子类别为Class2,换言之Class1为基底类别,Class2为衍生类别( 好吧,我也搞不懂为什么会出现那么多名词,反正就是Class2继承Class1就是了),而有时因为继承的物件( Class2 )的方法( Test )会和父类别( Class1 )的方法( Test )有不一样的处理逻辑,例如我同事就举例说,父类别是鸟,子类别是企鹅( 至于企鹅到底是不是鸟类,就不在这边讨论了),而鸟会有一个飞的方法,可以飞得远远的,但是子类别的企鹅,虽然也有飞的方法,但可能只能离地几公分;总之,子类别的方法名称虽然可能会和父类别的方法名称相同,但其实里面的逻辑不同,而这个时候,我们就可以在父类别的方法里面加上Virtual,子类别里面的方法加上override,来进行方法逻辑复写的动作,如下程式码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Class1
    {
        //如果沒有加上Virtual,子類別又override這個方法
        //編譯器就會生氣喔!
        public virtual void Test()
        {
            Console.WriteLine("Class1.Test()");
        }
    }

    public class Class2 : Class1
    {
        //這裡使用override來複寫
        public override void Test()
        {
            Console.WriteLine("Class2.Test()");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Class2 c = new Class2();
            c.Test();//理所當然會出現Class2.Test()

            //讓畫面可以暫停。
            Console.ReadLine();
        }
    }
}

 

所以到这边,画面上,会出现的是Class2.Test,也就是说,实际上是执行Class2的Test方法。

new

接下来,我们看一下下面这个程式码,我相信使用者一定不是故意的没写override,因为初学者可能不是很懂,小弟以前就干过这种事情,直接写了两个Class,也没override 。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Class1
    {
        //故意沒加上Virtual
        public void Test()
        {
            Console.WriteLine("Class1.Test()");
        }
    }

    public class Class2 : Class1
    {
        //這裡也不使用override關鍵字來複寫
        public void Test()
        {
            Console.WriteLine("Class2.Test()");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Class2 c = new Class2();
            c.Test();//結果還是會出現Class2.Test()

            //讓畫面可以暫停。
            Console.ReadLine();
        }
    }
}

 

这时,编译器就会不爽,如下图。

ooo隐藏了继承的成员xxx,如果是刻意要隐藏,请使用new关键字。

image

好,虽编译器不爽,但还是可以过,为什么呢?,我们可以从MSDN找到这串字

If the method in the derived class is not preceded by new or override keywords, the compiler will issue a warning and the method will behave as if the new keyword were present.

用小弟的破英文,稍微翻一下,意思就是说,假如这个方法没有new或是override这个两个关键字,伟大的编译器,就会很不爽地提醒你,但还是会很捞叨的帮你加上new这个关键字的效果。

所以就算不加上new和override还是可以正常编译,就是因为伟大的编译器帮你处理了,但没加上new这个关键字,编译器还是会很担心,怕使用者不知道它会自动地加上这个关键字,所以会冒出这个讯息来告知使用者"你现在的父类别方法xxx会隐藏起来,而实际会使用子类别的ooo方法,如果你真的是要这样的效果,请加上关键字new,来让编译器的我知道";听起来还不错啊,编译器其实不是不爽,而是关心你!,但目前new关键字和override关键字,看起来的效果一样阿,所以未来我们就可以不用加上new和override了吗!?毕竟加上new和override,看起来效果都差不多阿?,我承认我也有这样想过XDD,new和override虽然目前看起来的结果都一样,但真正的涵义是不同的,不然就不会出现这篇文章了XDD。

隐藏和复写

为什么那个警告是要用隐藏这字眼,而不是复写这词呢?,我们先看一下下面的程式码,这也是今天同事问我的问题,我们一样有Class1和Class2,但是在Class1上多加一个PreTest的方法,并在这个方法里面呼叫Test(),而Class2里面没有写PreTest方法,只有利用new关键字,来将Class1的Test方法隐藏起来,但毕竟Class2是继承Class1,所以实际上在执行时期,还是可以执行PreTest方法。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Class1
    {
        //多加一個方法
        public void PreTest()
        {
            Console.WriteLine("PreTest()");
            Test();
        }
        //
        public void Test()
        {
            Console.WriteLine("Class1.Test()");
        }
    }

    public class Class2 : Class1
    {
        //使用new關鍵字,讓編譯器開心一點。
        public new void Test()
        {
            Console.WriteLine("Class2.Test()");
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            
            Class2 c = new Class2();
            //改成呼叫c.PreTest()
            c.PreTest();//出現卻是Class1.Test()!!?

            //讓畫面可以暫停。
            Console.ReadLine();
        }
    }
}

 

好,结果出来了,答案却是Class1.Test(),这的确让我很错愕XDD,原本想说,这是Class2的实体,所以虽然透过Class1的PreTest来呼叫Test方法,但实际上应该也是要呼叫到Class2的Test方法阿,怎么会是呼叫到Class1的Test方法呢!?答案很简单,就出在new这个关键字;我们回过头来看看,为什么编译器警告时要用隐藏这个字眼,就是因为new的情况,只是会把Class1的Test隐藏起来,但实际上,无论是Class1或是Class2都还存着各自的Test方法;而C#的机制,当继承的Class2并无特别的撰写PreTest方法,所以会回到Class1里面去执行PreTest,当执行到Test()这行的时候,又因为Class2不是使用Override的方式来复写Class1的Test方法(也就是说实际上存着两个Test方法),所以就会直接使用Class1的方法;所以如果真的要执行Class2的Test方法,就必须要使用Override,也就是复写真正的含意,如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Class1
    {
        //多加一個方法
        public void PreTest()
        {
            Console.WriteLine("PreTest()");
            Test();
        }
        //
        public virtual void Test()
        {
            Console.WriteLine("Class1.Test()");
        }
    }

    public class Class2 : Class1
    {
        //使用override
        public override void Test()
        {
            Console.WriteLine("Class2.Test()");
        }

    }

    class Program
    {
        static void Main(string[] args)
        {
            
            Class2 c = new Class2();
            //改成呼叫c.PreTest()
            c.PreTest();//出現的是Class2.Test()

            //讓畫面可以暫停。
            Console.ReadLine();
        }
    }
}

 

这就是override和new最大的差异点。

多型后的override

这时候,可能因为读到后面章节,教到多型了( 如果都很懂得大大们,请体谅这些梗),所以我们就使用多型的方式,修改一下程式码,并使用override,如下。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Class1
    {
        //因為要使用override所以加上Virtual
        public virtual void Test()
        {
            Console.WriteLine("Class1.Test()");
        }
    }

    public class Class2 : Class1
    {
        //使用override關鍵字來複寫
        public override void Test()
        {
            Console.WriteLine("Class2.Test()");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //我們用個多型吧!
            Class1 c = new Class2();
            c.Test();//結果並不易外,出現的是Class2.Test()

            //讓畫面可以暫停。
            Console.ReadLine();
        }
    }
}

 

这边当然不意外的是出现Class2.Test,因为Class2复写了Class1的Test方法,所以呼叫c.Test()的时候,因为知道Class2有加上override关键字,所以实际是呼叫Class2的Test方法。

多形后的new

现在,我们在多形,并配合new,如下程式码。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Class1
    {
        //使用new,就可以不加上virtual
        public void Test()
        {
            Console.WriteLine("Class1.Test()");
        }
    }

    public class Class2 : Class1
    {
        //使用new關鍵字
        public new void Test()
        {
            Console.WriteLine("Class2.Test()");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //我們用個多型吧!
            Class1 c = new Class2();
            c.Test();//結果超易外,出現的是Class1.Test()

            //讓畫面可以暫停。
            Console.ReadLine();
        }
    }
}

 

吓到了吧,小弟我第一次看的确吓到XDD,这次出来的结果又不是原本预期的Class2.Test(),就如前面说的,new只是会隐藏Class1的Test,所以又会存在着两个Test方法,而C#预设的情况下,会先处理变数型别的方法,例如上面的程式码,变数型别为Class1,所以预设如果执行c.Test(),则会进行Class1的Test方法,除非Class2的Test有明确的加上override关键字,这时候,才会进行Class2的Test方法。

为什么要搞得那么复杂

讲到这边,差不多就把new和override可能发生的状况都讲过一次了,但为何C#要把事情弄得那么复杂,其实凡事必有因,会有这样的机制,是为了扩充上的弹性;假设目前的架构是Class1里面没有任何方法,而Class2里面有写一个Test方法,过了一段时间,可能Class1也加上了Test方法,这时候Class1和Class2也都有了Test方法了,此时,就算编译,依然是没有任何的问题的,如下。

Class1 c1 = new Class1();//不影響,還是呼叫class1的Test
c1.Test();
Class2 c2 = new Class2();//不影響,還是呼叫Class2的Test
c2.Test();
Class1 c3 = new Class2();//不影響,在Class1加上Test之前,是不可能這樣寫的
c3.Test();
Class2 c4 = new Class1();//怎麼可能會有這種寫法勒...

 

也因为这样,所以C#才会有new和override的一个差异,不过现实上,小弟我还是很少用到new就是了。

结论

写了那么多东西,基本的原则就是,如果是override,就和一般的继承没什么两样,但若是用到new,会先看变数型别来决定呼叫的是哪里的方法,如果这个变数型别里面的方法是继承而来的,就会回到父层去执行,而如果父层里面还有呼叫其他方法,只要子层没有override,则都会执行父层的方法。

后记

这篇写了整整三个小时,重新编排了三次= =,目的也是希望能看得比较懂XDD,这个东西也是C#很眉眉角角的东西,现实生活上,应该是很少用到new,但也难讲有哪天会遇到这种的设计方式,所以在这边纪录一下,希望未来忘记,还有地方可以找的到XDD。

 

 

原文:http://www.dotblogs.com.tw/skychang/archive/2012/05/10/72114.aspx

posted on 2014-07-09 15:53  忽而今夏  阅读(334)  评论(0编辑  收藏  举报