[你必须知道的.NET] 第五回:深入浅出关键字---把new说透
本文将介绍以下内容:
- 面向对象基本概念
- 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修饰符来完成。
例如:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
- 作为约束,用于在泛型声明中约束可能用作类型参数的参数的类型。
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在创建对象时到做了什么?
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
使用Reflector工具反编译产生的IL代码如下为:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
从而可以得出以下结论:
- 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 = "***************************************";