CLR via C#学习笔记-第八章-实例构造器和结构(值类型)

8.2 实例构造器和结构(值类型)

值类型struct构造器的工作方式与引用类型class的构造器截然不同。

CLR总是允许创建值类型的实例,并且没有办法阻止值类型的实例化。所以,值类型其实并不需要定义构造器。

C#编译器根本不会为值类型内联默认的无参构造器。来看下面的代码。

internal struct Point
{
    public Int32 m_x,m_y;
}
internal sealed class Rectangle
{
    public Point m_topLeft,m_bottomRight;
}

为了构造一个Rectangle,必须使用new操作符,而且必须指定构造器。在各个例子中,调用的是C#编译器自动生成的默认构造器。

为Rectangle分配内存时,内存中包含Point值类型的两个实例。考虑到性能,CLR不会为包含在引用类型中的每个值类型字段都主动调用构造器。

但是,如前所述,值类型的字段会被初始化为0或null。

如何调用值类型结构的构造器

CLR确实允许为值类型定义构造器。但是必须显式调用才会执行。下面是一个例子。

internal struct Point
{
    public Int32 m_x,m_y;
    public Point(Int32 x,Int y)
    {
        m_x=x;
        m_y=y;
    }
}
internal sealed class Rectangle
{
    public Point m_topLeft,m_bottomRight;
    public Rectangle()
    {
        //在C#中,向一个值类型应用关键字new
        //可以调用构造器来初始化值类型的字段
        m_topLeft=new Point(1,2);
        m_bottomRight=new Point(100,200);
    }
}

值类型的构造器只有显式调用时才会执行。因此如果Rectangle的构造器没有使用new操作符来调用Point的构造器,从而初始化其的两个字段,那么两字段都将为0。

值类型不允许定义无参构造器

前面展示的Point值类型没有定义默认的无参构造器。现在进行如下改写。

internal struct Point
{
    public Int32 m_x,m_y;
    public Point()
    {
        m_x=m_x=5;
    }
}
internal sealed class Rectangle
{
    public Point m_topLeft,m_bottomRight;
    public Rectangle(){}
}

现在构造新的Rectangle类时,当两个Point字段中的m_x,m_y会被初始化为多少?

为了增强应用程序的运行时性能,C#编译器不会自动生成为Rectangle的两个字段调用Point默认无参构造器的代码。

实际上,即使值类型提供了无参构造器,许多编译器也永远不会自动生成这种代码来自动调用它。

为了执行值类型的无参构造器,开发人员必须增加显式调用值类型构造器的代码。

基于前面的信息可以确定两个字段会被初始化为0。但实际上前面的代码是无法编译的,因为C#编译器故意不允许值类型定义无参构造器,目的是防止开发人员对这种构造器在什么时候调用产生疑惑。

由于不能定义无参构造器,所以编译器永远不会自动生成调用它的代码。没有无参构造器,值类型总是被初始化为0或null

提示

严格地说,只有当值类型的字段嵌套到引用类型时,才保证被初始化为0或null。基于栈的值类型字段则无此保证。

但是为了确保代码的可验证性verifiability,任何基于栈的值类型字段都必须在读取之前写入赋值。

值类型不能用内联的方式初始化字段

虽然C#不允许值类型定义无参构造器,但CLR允许。

由于C#不允许为值类型定义无参构造器所以编译以下类型时,C#会报错:结构中不能用实例字段初始值设定项。

internal struct SomeValType
{
    //不能在值类型中内联实例字段的初始化
    private Int32 m_x=5;  
}

有参构造器必须初始化所有字段

另外为了生成可验证代码,在访问值类型的任何字段之前,都需要对全部字段进行赋值。

所以值类型的任何构造器都必须初始化值类型的全部字段。以下类型为值类型定义了一个构造器,但没有初始化值类型的全部字段。

internal struct SomeValType
{
    private Int32 m_x,m_y;
    //C#允许为值类型定义有参构造器
    public SomeValType(Int32 x)
    {
        m_x=x;
        //注意m_y没有在这里初始化
    }
}

编译上述类型,C#会报错:在控制返回到调用方法之前,字段SomeValType.m_y必须完全赋值。

为了修正这个问题,需要早构造器中为y赋一个值,通常是0。下面是值类型的全部字段进行赋值的一个代替方案。

public SomeValType(Int32 x)
{
    //看起来很奇怪,但编译没问题,会将所有字段初始化为0/null
    this=new SomrValType();
    m_x=x;
}

在值类型的构造器中,this代表值类型本身的一个实例,用new创建的值类型的一个实例可以赋给this。

在new的过程中,会将所有字段置为0,而在引用类型的构造器中,this被认为是只读的,所以不能对它进行赋值。

猜你喜欢

转载自www.cnblogs.com/errornull/p/9769331.html
今日推荐