C# struct

简介

C/C++程序员或多或少都有使用struct的经历,在C++structclass的区别不大,除了默认成员的可访问性,这点在C#中则截然不同。本文将力图说明C#structclass的区别以及如何正确的使用struct

为什么需要struct?

众所周知,在java中并没有struct的概念,那么C#为何引入struct呢?最基本原因是可以创建值类型的类型,使在托管环境中有更好的性能。

区别于javaC#有值类型和引用类型的概念(java只有引用类型)。引用类型的实例分配在堆上,当对象没有被引用时被垃圾回收器回收;值类型的实例分配在栈上,当离开其作用域后内存被回收。值类型本身存储的是值本身,引用类型存储的是引用,C#语言提供的原始类型除了string类型其它都是值类型。

C#struct是值类型,class是引用类型,可以通过enumstruct关键字创建值类型的对象。使用值类型可以减少托管堆上对象的数量,从而减少垃圾回收器(GC)的负担,提高性能,值类型也有明显的缺点:通过值类型传递较大对象的开销比较昂贵、装箱和拆箱对性能造成影响。

Classes 和Structs

 public struct Employeestruct
    {
        private int _fooNumber;

        public Employeestruct(int fooNumber)
            : this()
        {
            _fooNumber = fooNumber;
        }

        public string Name { get; set; }

        public int Age { get; set; }

        public string GetMessage()
        {
            return string.Format("{0}--{1}", Name, Age);
        }
    }

从上面可以看到,structclass非常相似,不过它们还是有本质的区别,接下来我们就一一分析。

1. Structs 和Inheritance

StructsSystem.ValueType继承而classes继承于System.Object或从System.Object继承的其他类型,当然System.ValueType也从System.Object继承(这不是重点)Structs可以实现接口,但不能从另一个classesstructs继承,而且不能作为其他classes的基类。要知道,当你把structs作为接口使用时,就进行一次隐形装箱,因为接口作为引用类型。请看下面代码:

struct Foo : IFoo
{ 
      int x;
}

IFoo iFoo = new Foo();

Foo的实例被创建并赋值给iFoo(装箱),当iFoo调用时实际上使用的是Foo装箱后的实例。

2. Constructors

尽管CLR允许,但是不允许在structs中定义无参的构造函数。对于值类型,编译器默认情况下不生成默认的构造函数,也不调用默认的构造函数,所以C#编译器不允许使用者定义无参的构造函数。由于structs不生成默认的构造函数,所以不能初始化字段。如下(错误):

Struct MyFoo
{
        int x = 1;
}

记住,编译器把初始化工作放在构造函数中,由于structs没有默认的构造函数,所以不能初始化字段。

有趣的是,你可以使用下面的语法:

Foo foo  =  new Foo();

通过之前的章节了解到,尽管初始化foo使用了new操作符,但是structs的实例分配到栈上。new Foo()不会调用无参的构造函数,仅仅是初始化该struct的字段为默认值。

struct Foo
{
       int x;

       public Foo(int x)
       {
           this.x = x;
       }
}    

Foo foo  =  new Foo();

注意,我已经重载了构造函数,然而我能够调用new Foo()

3. Destructors

Structs不允许定义析构函数,如果你扔就去定义一个析构函数,编译器会立即提示一个错误,不过structs可以实现IDisposable接口。

4. Comparison against null

Structs不能和null进行比较,这点在.net framework2.0已不再是问题(可空类型),关于可空类型,超出本文讨论范围。

5. Readonly关键字

对于引用类型,readonly阻止再为变量赋值,但不阻止你修改当前引用对象的状态。对于值类型,readonly有点类似C++中的const关键字,它阻止你修改引用对象,也意味着不能再为他赋值,因为这会导致重新初始化一次。请看下面代码:

    public class MyReferenceType
    {
        public int State { get; set; }
    }

    public struct MyValueType
    {
        public int State { get; set; }
    }

   public void TestMethod()
    {
        myReferenceType = new MyReferenceType(); // 错误
        myReferenceType.State = 1234; // Ok

        myValueType = new MyValueType(); // 错误
        myValueType.State = 1234; //  错误
    }

foreach 语句和using语句的变量为隐式readonly,所以如果使用structs,将无法更改其状态。

何时使用 structs

通过前面的描述已经清楚classesstructs的区别,那我们来看下适合使用structs的场景:

l 实例使用起来像C#的基元类型

l 需要创建大量的、短暂实例(例如在循环体内)

l 实例不需要大量的传递

l 不需要从其他类型继承或不希望被其他类型继承

l 希望被人操作你实例的副本

不适合使用structs的场景:

l 实例过大,当实例过大时,传递实例会有很大的开销。微软建议structs的理想大小应该在16bytes以下

l 当回引起装箱、拆箱操作时。关于装箱、拆箱超出本文讨论范围

原文地址:https://www.cnblogs.com/oxdavidsun/p/4835361.html