《CLR via C#》读书笔记(5)基元类型、引用类型和值类型

2023-06-12,,

5.1
基元类型

编译器直接支持的数据类型称为基元类型(primitive type)。
以下4行到吗生成完全相同的IL

            int             a = 0;                  //最方便的语法
            System.Int32    b = 0;                  //方便的语法
            int             c = new int();          //不方便的语法
            System.Int32    d = new System.Int32(); //最不方便的语法 

C#基元类型与对应的FCL类型

C#中的基元类型

FCL类型

是否与CLS兼容

描述

sbyte

System.SByte

N

有符号8位值

byte

System.Byte

Y

无符号8位值

short

System.Int16

Y

有符号16位值

ushort

System.UInt16

N

无符号16位值

int

System.Int32

Y

有符号32位值

uint

System.UInt32

N

无符号32位值

long

System.Int64

Y

有符号64位值

ulong

System.UInt64

N

无符号64位值

char

System.Char

Y

16位Unicode字符(不像非托管C++中那样,char表示的是一个8位值)

float

System.Single

Y

IEEE32位浮点数

double

System.Double

Y

IEEE 64位浮点数

bool

System.Boolean

Y

一个True或者Flase值

decimal

System.Decimal

Y

128位高精度浮点值,通常用于不容许有摄入误差的金融计算场合。在这128位中,1位表示浮点值的符号,96位表示浮点值本身(一个整数值,小数点位置由下面8个位来确定),8位表示用96位值除以浮点值所得结果的10的幂次(0~28)。其余的位尚未使用

string

System.String

Y

一个字符数组

object

System.Object

Y

所有类型的基类型

dynamic

System.Object

Y

对于CLR, dynamic 和 object 完全一致。然而,C#编译器允许使用一个简单的语法,让dynamic变量参与动态调度。

checked 和 unchecked 的建议

尽量使用有符号的数值类型(比如 Int32 和 Int64),而不要使用无符号的数值类型(比如 UInt32 和 UInt64),这允许编译器检测更多的上溢/下溢错误。
如果代码发生不希望的溢出(可能因为无效的输入数据发生的),就把这些代码放在 checked 块中。同时还应捕捉 OverflowException。
将允许发生溢出的代码显示的放在一个 unchecked 块中,比如一个计算校验和(checksum)的时候。

5.2
引用类型和值类型
引用类型
任何称为类的类型都是引用类型。

引用类型总是从托管堆上分配
对象中的其他字节总设为零
从托管堆上分配一个对象时,可能强制执行一次垃圾收集操作

值类型

所有值类型都称为结构或枚举。

值类型一般在线程栈上分配
值类型并不包含一个指向实例的指针,相反,它包含了实例本身的字段,
值类型的实例不受垃圾回收站的控制
所有结构都是 System.ValueType 直接派生类,所有枚举都是从 System.Enum 的抽象类派生,后者又是从 System.ValueType 派生

值类型对象有两种表现形式:未装箱(unboxed)与已装箱(boxed),相反,引用类型总是处于已装箱形式。

5.3
值类型的装箱和拆箱

1装箱

    struct Point
    {
        public Int32 x, y;
    }

    public sealed class Program1
    {
        public static void Main()
        {
            ArrayList a = new ArrayList();
            Point p;
            for (int i = 0; i < 10; i++)
            {
                p.x = p.y = i;
                a.Add(p);  //对值类型进行装箱,并将引用添加到 ArrayList 中
            }

            Console.Read();
        }
    } 

Add 需要获取对托管堆的一个对象引用(或指针)作为参数。这里的 P 是一个结构(值类型),所以要使用装箱(boxing)。
在装箱操作时内部发生的事情:

1.在托管堆中分配内存。分配的内存量是值类型的各个字段需要的内存量加上托管堆的所有对象都有的两个额外成员(类型对象指针和同步索引块)需要的内存量。

2.值类型的字段赋值到新分配的堆内存。

3.返回对象的地址。值类型现在是一个引用类型。

2拆箱

拆箱就是一个获取指针的过程。事实上,指针指向的是已装箱实例中的未装箱部分。

            Int32 x = 5;
            Object o = x;
            Int16 y = (Int16)o; //抛出一个“InvalidCastException”类型的未经处理的异常 

只有将其转换为原先未装箱时的值类型——为 Int32
Int16 y = (Int16)(Int32)o;  //先拆箱为正确的类型,再转换

下面代码演示了涉及装箱和拆箱的几种情形

    class CLass2
    {
        static void Main()
        {
            Point p1 = new Point(10, 10);
            Point p2 = new Point(20, 20);

            //调用ToString(一个虚方法)时,不会对p1进行装箱
            Console.WriteLine(p1.ToString());

           //调用GetType(一个非虚方法)时,要对p进行装箱
            Console.WriteLine(p1.GetType());

            //第一次调用CompareTo时,不会对p1进行装箱
            //由于调用的是CompareTo(Point),所以对p2不会装箱
            Console.WriteLine(p1.CompareTo(p2));

            //p1要进行装箱,将引用放到c中
            IComparable c = p1;
            Console.WriteLine(c.GetType());

            //第二次调用CompareTo时,p1不需要装箱
            //由于向CompareTo传递的不是一个Point变量,所以调用CompareTo(object),要求获取一个已装箱的 Point 的引用
            //c不会装箱,因为它引用的本来就是一个已经装箱的 Point
            Console.WriteLine(p1.CompareTo(c));

            //第上次调用CompareTo时,C不需要装箱,因为它引用的本来就是一个已经装箱的Point
            //p2会被装箱,因为调用的是CompareTo(object)
            Console.WriteLine(c.CompareTo(p2));

            //对 c 进行拆箱,字段复制到 p2 中
            p2 = (Point)c;

            //证明字段已经复制到p2中
            Console.WriteLine(p2.ToString());
            Console.Read();
        }
    }

    internal struct Point : IComparable
    {
        private Int32 m_x, m_y;

        public Point(Int32 x, Int32 y)
        {
            m_x = x;
            m_y = y;
        }

        public override string ToString()
        {
            return string.Format("{0},{1}", m_x, m_y);
        }

        //实现类型安全的 CompareTo方法
        public Int32 CompareTo(Point other)
        {
            //使用勾股定力来计算哪个point的距离原点(0,0)更远
            return Math.Sign(
                Math.Sqrt(m_x * m_x + m_y * m_y) - Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y)
                );
        }
        //实现IComparable的CompareTo方法
        public int CompareTo(object obj)
        {
            if (GetType() != obj.GetType())
            {
                throw new ArgumentException("obj is not a Point");
            }
            //调用类型安全的CompareTo方法
            return CompareTo((Point)obj);
        }
    } 
5.4
dynamic

许多时候程序需要处理一些运行时才会知晓的信息。许多开发者在使用C#时,要和一些不是用C#实现的组件通信,如 Python或Ruby。为了方便开发人员使用反射或者与基本组件通信,C#编辑器允许将一个表达式的类型标记为 dynamic。或者变量类型标记为 dynamic。可以用这个 dynamic 表达式/变量调用一个成员,比如字段。属性/索引器、方法、委托等等。编译器会生成特殊的IL代码来描述所需要的操作。这个特殊的代码称为 payload(有效载荷)。

    internal class DynamicDemo
    {
        static void Main()
        {
            for (int demo = 0; demo < 2; demo++)
            {
                dynamic arg = (demo == 0) ? (dynamic)5 : (dynamic)"A";
                dynamic result = Plus(arg);
                M(result); 
                //M(Int32):10 
                //M(String):AA
            }
            Console.Read();
        }

        private static dynamic Plus(dynamic arg) { return arg + arg; }
        private static void M(int n) { Console.WriteLine("M(Int32):" + n); }
        private static void M(string s) { Console.WriteLine("M(String):" + s); }
    } 

在字段类型、方法参数类型或方法返回类型被指定为 dynamic 前提下,编译器会将这个类型转为 System.Object,并在元数据中向字段、参数或返回类型应用  System.Runtime.CompilerServices.DynamicAttribute 的一个实例,局部变量类型也为 Object ,但不会应用 DynamicAttribute ,因为它使用限制在方法内。

来自为知笔记(Wiz)

《CLR via C#》读书笔记(5)基元类型、引用类型和值类型的相关教程结束。

《《CLR via C#》读书笔记(5)基元类型、引用类型和值类型.doc》

下载本文的Word格式文档,以方便收藏与打印。