new说透 C#---.net 学习

来源:互联网 发布:windows配置ntp客户端 编辑:程序博客网 时间:2024/05/17 05:13

原创 new说透 C#---.net 学习

一般说来,new关键字在.NET中用于以下几个场合,这是MSDN的典型解释:

  • 作为运算符, 用于创建对象和调用构造函数。

本文的重点内容,本文在下一节来重点考虑。

  • 作为修饰符,用于向基类成员隐藏继承成员。

作为修饰符,基本的规则可以总结为:实现派生类中隐藏方法,则基类方法必须定义为virtual;new作为修饰符,实现隐藏基类成员时,不可和override共存,原因是这两者语义相斥:new用于实现创建一个新成员,同时隐藏基类的同名成员;而override用于实现对基类成员的扩展。

另外,如果在子类中隐藏了基类的数据成员,那么对基类原数据成员的访问,可以通过base修饰符来完成。

例如: 


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在创建对象时到做了什么? 


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代码如下为: 


.method public hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 
2
    .locals init (
        [
0] int32 num,
        [
1] int32 num2,
        [
2class 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 = "***************************************"

原创粉丝点击