[你必须知道的.NET] 第五回:深入浅出关键字---把new说透

本文将介绍以下内容:
  • 面向对象基本概念
  • new关键字深入浅出
  • 对象创建的内存管理 

1. 引言

园子里好像没有或者很少 把new关键字拿出来说的,那我就占个先机吧,呵呵。那么,我们到底有必要将一个关键字拿出来长篇 大论吗?看来是个问题。回答的关键是:你真的理解了new吗?如果是,那请不要浪费时间,如果不 是,那请继续本文的循序之旅。

下面几个 问题可以大概的考察你对new的掌握,开篇之前,希望大家做个检验,如果通过了,直接关掉本页即 可。如果没有通过,希望本文的阐述能帮你找出答案。

  1. new一个class对 象和new一个struct或者enum有什么不同?
  2. new在.NET中 有几个用途,除了创建对象实例,还能做什么?
  3. new运算符,可以重载吗?
  4. 范型中,new有 什么作用?
  5. new一个继承下来的方法和override一 个继承方法有何区别?
  6. int iint 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 
    
    
指 令创建新的对象,并调用构造函数以0x76(123的16进制)初始化
    L_0003: ldc.i4.s 0x7b    
    L_0005: newobj instance 
 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_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关键字和其本质的来龙去脉能有 所帮助。 

言归正传,开篇的几个题 目,不知读者是否有了各自的答案,我们不妨畅所欲言,做更深入的讨论,以便揭开其真实的面纱。
posted @ 2010-02-06 22:46  与时俱进  阅读(207)  评论(0编辑  收藏  举报
友情链接:同里老宅院民居客栈