[你必須知道的.NET] 第五回:深入淺出關鍵字---把new說透
作者:Anytao
本文將介紹以下內容:
- 物件導向基本概念
- 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 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#的話題,希望大家理清概念,有的放肆。