程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> [你必須知道的.NET] 第五回:深入淺出關鍵字---把new說透

[你必須知道的.NET] 第五回:深入淺出關鍵字---把new說透

編輯:關於.NET

相關文章:

[你必須知道的.NET] 第三回:歷史糾葛:特性和屬性

[你必須知道的.NET] 第四回:後來居上:class和struct

本文將介紹以下內容:

面向對象基本概念

new關鍵字深入淺出

對象創建的內存管理

1.引言

園子裡好像沒有或者很少把new關鍵字拿出來說的,那我就占個先機吧,呵呵。那麼,我們到底有必要將一個關鍵字拿出來長篇大論嗎?看來是個問題。回答的關鍵是:你真的理解了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 Anytao.net.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<T> where T : new()
     {
         public T GetItem()
         {
             return new T();
         }
}

實現方式為:

class MyCls
     {
         private string _name;

         public string Name
         {
             get { return _name; }
             set { _name = value; }
         }

         public MyCls()
         {
             _name = "Emma";
         }
     }

     class MyGenericTester
     {
         public static void Main(string[] args)
         {
             Genericer<MyCls> MyGen = new Genericer<MyCls>();
             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 Anytao.net.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 Anytao.net.My_Must_net._05_new.MyClass class2,
         [3] valuetype Anytao.net.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 Anytao.net.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 Anytao.net.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關鍵字和其本質的來龍去脈能有所幫助。

言歸正傳,開篇的幾個題目,不知讀者是否有了各自的答案,我們不妨暢所欲言,做更深入的討論,以便揭開其真實的面紗。

參考文獻

(USA)Stanley B.Lippman, C# Primer

(USA)David Chappell Understanding .NET

廣而告之

[預告]

另外鑒於前幾個主題的討論中,不管是類型、關鍵字等都涉及到引用類型和值類型的話題,我將於近期發表相關內容的探討,同時還有其他的關鍵字值得研究,這是本系列近期動向,給自己做個廣告。祝各位愉快。

[聲明] 

本文的關鍵字new指的是C#中的關鍵字概念,並非一般意義上的.NET CRL范疇,之所以將這個主題加入本系列,是基於在.NET體系下開發的我們,何言能逃得過基本語言的只是要點。所以大可不必追究什麼是.NET,什麼是C#的話題,希望大家理清概念,有的放肆。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved