设计类型(二):基元类型、引用类型和值类型

本章要讨论的是.net的各种类型。这章开始,我想摒弃以前的抄书模式,尝试自己阅读后先行总结,然后再写博客。

基元类型

所谓基元类型,指的是编译器直接支持的数据类型。基元类型直接映射到Framework类库中存在的类型。下面四行代码可以生成完全相同的IL:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace Program1
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             int a = 0;
14             Int32 b = 0;
15             int c = new int();
16             Int32 d = new Int32();
17         }
18     }
19 }

再看他们的IL代码:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       10 (0xa)
  .maxstack  1
  .locals init (int32 V_0,
           int32 V_1,
           int32 V_2,
           int32 V_3)
  IL_0000:  nop
  IL_0001:  ldc.i4.0
  IL_0002:  stloc.0
  IL_0003:  ldc.i4.0
  IL_0004:  stloc.1
  IL_0005:  ldc.i4.0
  IL_0006:  stloc.2
  IL_0007:  ldc.i4.0
  IL_0008:  stloc.3
  IL_0009:  ret
} // end of method Program::Main

由此可知,这四个写法是完全等价的。

在本书中,坚持使用FCL名称,主要有以下原因:

1.很多人纠结于使用string还是System.String,其实这两者没有区别。类似的,还有int和Int32:C#的int永远映射到Int32.C#的long固定映射到Int64.

2.FLC的许多方法都将类型名作为方法名的一部分。

3.方便些其他面向CLR的代码(代码风格一致)。

 在高精度基元类型隐式转换到低精度基元类型的时候,往往会进行截断处理(区别于向上取整)。

C#自带checked操作符来在特定的区域控制溢出检查:

       Byte b = 100;
            b = checked((Byte)(b + 200));

会抛出异常:

还可以使用checked语句:

1         static void Main(string[] args)
2         {
3             checked {
4                 Byte b = 100;
5                 b = (Byte)(b + 200);
6             }
7             
8         }

结果是一样的。如果使用了checked语句块,还可以将+=应用于Byte:

 1     class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             checked {
 6                 Byte b = 100;
 7                 b += 200;
 8             }
 9             
10         }
11     }

在日常编程时,给予诸位如下建议:

1.尽量使用有符号数值类型Int32之类而不是UInt32,这样编译器会检查更多的上溢下溢。此外,类库中的很多方法的返回值都是有符号的,这样子可以减少强制类型转换。以及,无符号数值类型不符合CLS。

2.如果代码可能发生溢出,请放到checked语句块中。

3.将允许溢出的代码放到unchecked中。

4.对于没有使用checked和unchecked的代码,溢出默认会抛出异常,

引用类型和值类型

首先,要认清楚四个事实:

1.内存必须从托管堆中分配;

2.堆上的每一个对象都有额外成员,这些成员必须初始化;

3.对象的其他字节总是为零;

4.从托管堆分配对象时,可能强制执行一次GC。

因此,使用引用类型而非值类型的时候,性能会下降。在设计自己的类型时,要考虑是否应该定义成值类型而不是引用类型。除非满足以下全部条件,否则不应该声明为值类型:

1.类型具有基元类型的行为,是不可变类型(没有提供会更改其字段的成员);

2.不需要从其他任何类型继承;

3.没有派生类型;

4.类型实例较小(小于等于16字节);

5.实例类型较大,但不作为方法传递实参,也不从方法返回。

列出值类型和引用类型的一些区别:

1.值类型有两种形式:已装箱和未装箱。引用类型总是处于已装箱;

2.值类型从ValueType派生

#region 程序集 mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\mscorlib.dll
#endregion

using System.Runtime.InteropServices;
using System.Security;

namespace System
{
    //
    // 摘要:
    //     提供值类型的基类。
    [ComVisible(true)]
    public abstract class ValueType
    {
        //
        // 摘要:
        //     初始化 System.ValueType 类的新实例。
        protected ValueType();

        //
        // 摘要:
        //     指示此实例与指定对象是否相等。
        //
        // 参数:
        //   obj:
        //     要与当前实例进行比较的对象。
        //
        // 返回结果:
        //     如果 obj 和该实例具有相同的类型并表示相同的值,则为 true;否则为 false。
        [SecuritySafeCritical]
        public override bool Equals(object obj);
        //
        // 摘要:
        //     返回此实例的哈希代码。
        //
        // 返回结果:
        //     一个 32 位有符号整数,它是该实例的哈希代码。
        [SecuritySafeCritical]
        public override int GetHashCode();
        //
        // 摘要:
        //     返回该实例的完全限定类型名。
        //
        // 返回结果:
        //     包含完全限定类型名的 System.String。
        public override string ToString();
    }
}

而ValueType继承自System.Object;

3.不能在值类型中加入虚方法,所有的方法都不能抽象,不可重写;

4.引用类型包含了堆中对象的地址。引用类型变量在创建的时候默认初始化为NULL,而值类型总是0。null引用类型会抛出异常。值类型可以添加可空标识;

5.值类型复制是完全拷贝,而引用类型只拷贝地址;

6.

猜你喜欢

转载自www.cnblogs.com/renzhoushan/p/10410658.html
今日推荐