CLR via C#:类型基础(基元类型,引用类型和值类型)

基础知识:如下所示:
1.所有的类型都是显示或者隐式的继承自System.Object。
2.CLR在查找类型时会先从当前文件中进行查找,找不到时就会从引用的程序集中进行查找,如果还是找不到就会从命令空间中进行查找。如果查找的类型具有二义性的话,可以使用以下方案解决:
1>.在类型前面加上明确的命名空间前缀。
2>.使用using操作符来定义"命名空间.类型"的别名,然后使用别名来当做类型使用。
3>.当外部存在命名空间和类型名都相同时,可以使用外部别名来解决。
3.var关键字只能用来修饰局部变量,目的是让编译器根据初始化的值来推断出数据类型。

溢出处理:当数据进行运算操作时容易出现精度或者数量级的丢失,此时就会出现数据溢出问题。具有以下特性:
1.c#中的checked操作符可以对代码进行溢出检测,如果有溢出的话就会抛出异常。
2.c#中的unchecked操作符不对代码进行溢出检测。
3.编译器的/checked+开关可以对代码进行溢出检测,如果有溢出的话就会抛出异常。
4.编译器的/checked-开关不对代码进行溢出检测。
5.不论是编译器开关还是c#操作符,进行溢出检测设置实际上都是为了生成具有溢出检测的IL指令,如:add.ovf(加),sub.ovf(减),mul.ovf(乘),cov.ovf(转换)。
6.不论是编译器开关还是c#操作符,不进行溢出检测设置实际上都是为了生成没有溢出检测的IL指令,如:add(加),sub(减),mul(乘),cov(转换)。

类型转换:如下所示:
1.子类转换成父类时可以隐式转换,但是父类转换成子类时必须显示转换。
2.is操作符用来判定对象是否为指定类型,如果是就返回true,否则返回false。对null对象使用is操作符时会永远返回false。
3.as操作符用来将对象转换成指定类型对象,如果转换成功就返回该指定类型对象,否则就返回null。对null对象使用as操作符会永远返回null。

类型相等性和同一性:如下所示:
1.应该使用Object的ReferenceEquals函数来判断两个类型是否具有同一性。
2.由于引用类型调用Object的Equals函数时只是用来判断两个类型是否具有同一性;值类型调用ValueType重写的Equals函数时只是以低效的反射方式来判断两个类型是否具有相等性;所以程序员应该自己重写Equals函数来判断两个类型是否具有相等性。
重写Equals的条件如下所示:前4条是必须满足的,后面2条是可选的。
1>.Equals必须自反;如:x.Equals(x)肯定返回true。
2>.Equals必须对称;如:x.Equals(y)和y.Equals(x)返回相同的值。
3>.Equals必须可传递;如:x.Equals(y)返回true,y.Equals(z)返回true,那么x.Equals(z)肯定返回true。
4>.Equals必须一致;如:比较的值不变,Equals返回值也不能变。
5>.可以选择性的让类型实现System.IEquatable接口中类型安全的Equals方法并且在该Equals方法中实现1,2,3,4条件的代码;然后可以选择性的重载==和!=操作符方法,并在内部调用该类型安全的Equals方法;最后在重写Object的Equals方法中也调用该类型安全的Equals方法。
6>.在实现排序功能时可以选择的让类型实现System.IComparable接口中类型安全的CompareTo方法;然后可以选择性的重载< <= > >=操作符方法以及实现System.IComparable接口中的CompareTo方法,并在内部调用该类型安全的CompareTo方法;最后在该类型安全的CompareTo方法中调用类型安全的Equals方法。
重写Equals的代码如下所示:只针对前4条进行编码,后面2条暂时不考虑

public override bool Equals(object obj)
{
    // 要比较的对象也不能为空
    if (obj == null)
    {
        return false;
    }
    
    // 对象必须具有相同的类型
    if (this.GetType() != obj.GetType())
    {
        return false;
    }
    
    // this和obj只要存在任何一个字段值不相同就不具有相等性
    
    return true;
}

对象哈希码:在重写Equals函数时最好也重写下GetHashCode函数,从而保证相等性算法和哈希码算法一致。在重写GetHashCode函数时应该注意以下规则:
1.算法的执行速度要快。
2.算法要提供良好的随机分布,使哈希表获得最佳性能。
3.算法至少使用一个属性字段且使用的属性字段应该是不可变的。
4.算法中可以包含基类中的GetHashCode函数返回值,但是不要包含低性能的Object和ValueType的GetHashCode函数返回值。
5.包含相同值得不同对象应返回相同的哈希码。
6.不要对哈希码进行序列化操作,因为GetHashCode的算法一变,哈希码也就不一样了,之前序列化的数据也就丢失了。

基元类型:编译器直接支持的数据类型,在FCL中都有对应的关联类型。具有以下特性:
1.C#中定义的基元类型在FCL中都有关联的数据类型,这些基元类型有些符合CLS,有些不符合CLS。如下表所示:

C#基元类型FCL类型是否符合CLS说明
sbyteSystem.SByte有符号8位整形值
byteSystem.Byte无符号8位整形值
shortSystem.Int16有符号16位整形值
ushortSystem.UInt16无符号16位整形值
intSystem.Int32有符号32位整形值
uintSystem.UInt32无符号32位整形值
longSystem.Int64有符号64位整形值
ulongSystem.UInt64无符号64位整形值
floatSystem.SingleIEEE32位浮点值
doubleSystem.DoubleIEEE64位浮点值
decimalSystem.Decimal128位高精度浮点值。128位中,1位是符号,96位是值本身(N),8位是比例因子(k)。decimal实际值为:±N*10^k,其中-28<= k <= 0。其余位没有使用。
charSystem.Char16位unicode字符
stringSystem.String字符数组
boolSystem.Booleantrue/false值
objectSystem.Object所有类型的基类型
dynamicSystem.ObjectCLR中dynamic与object完全一致

2.建议使用FCL类型代替基元类型进行编码,因为这样可以更加清晰的知道占用的位数等信息,避免在不同语言中使用时出现位数不匹配问题。
3.基元类型之间进行转换时,如果不存在转换后丢失精度或者数量级的话,可以进行隐式或者显式转换;否则就必须使用显式转换。
4.dynamic基元类型具有以下特性:
1>.c#编译器允许使用dynamic修饰表达式或者实例,从而方便程序员使用反射或者与其他组件之间进行通信。
2>.dynamic实际上就是FCL中的Object。所有类型都能隐式转换成dynamic,而且dynamic也能够隐式的转换成其他类型(Object必须显示转换成其他类型)。
3>.dynamic修饰的实例在调用非静态成员(属性,方法等)时,编译器会生成payload代码。当payload代码在执行时会使用运行时绑定器产生驻留程序集的动态代码(额外的开销,所以尽量不要使用dynamic)。该动态代码会检查实例的类型是否实现了IDynamicMetaObjectProvider接口,如果有实现的话就会调用该接口的GetMetaObject方法来获取一个DynamicMetaObject的派生类型,该派生类型会处理实例的所有属性,方法和操作符绑定;如果没有实现的话就会利用反射在实例上执行操作。
4>.dynamic修饰的实例在调用静态成员(属性,方法等)时会抛出异常;此时可以使用反射的方式来实现,代码如下所示:

// 静态方法调用工具类:
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;

namespace TestCLR2
{
    internal sealed class StaticMemberDynamicWrapper : DynamicObject
    {
        private readonly TypeInfo m_type;

        public StaticMemberDynamicWrapper(Type type)
        {
            m_type = type.GetTypeInfo();
        }

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return m_type.DeclaredMembers.Select(mi => mi.Name );
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = null;
            var field = FindField(binder.Name);
            if (field != null)
            {
                result = field.GetValue(null);
                return true;
            }

            var prop = FindProperty(binder.Name, true);
            if (prop != null)
            {
                result = prop.GetValue(null, null);
                return true;
            }

            return false;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            var field = FindField(binder.Name);
            if (field != null)
            {
                field.SetValue(null, value);
                return true;
            }

            var prop = FindProperty(binder.Name, false);
            if (prop != null)
            {
                prop.SetValue(null, value, null);
                return true;
            }

            return false;
        }

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            MethodInfo method = FindMethod(binder.Name, args.Select(c => c.GetType()).ToArray());
            if (method == null)
            {
                result = null;
                return false;
            }

            result = method.Invoke(null, args);
            return true;
        }

        private MethodInfo FindMethod(String name, Type[] paramTypes)
        {
            return m_type.DeclaredMethods.FirstOrDefault(mi => mi.IsPublic &&
                                                               mi.IsStatic &&
                                                               mi.Name == name &&
                                                               ParameteMatch(mi.GetParameters(), paramTypes));
        }

        private Boolean ParameteMatch(ParameterInfo[] parameters, Type[] paramTypes)
        {
            if (parameters.Length != paramTypes.Length)
            {
                return false;
            }

            for (Int32 i = 0; i < parameters.Length; i++)
            {
                if (parameters[i].ParameterType != paramTypes[i])
                {
                    return false;
                }
            }

            return true;
        }

        private FieldInfo FindField(String name)
        {
            return m_type.DeclaredFields.FirstOrDefault(fi => fi.IsPublic &&
                                                              fi.IsStatic &&
                                                              fi.Name == name);
        }

        private PropertyInfo FindProperty(String name, Boolean get)
        {
            if (get)
            {
                return m_type.DeclaredProperties.FirstOrDefault(pi => pi.Name == name &&
                                                                      pi.GetMethod != null &&
                                                                      pi.GetMethod.IsPublic &&
                                                                      pi.GetMethod.IsStatic);
            }
            else
            {
                return m_type.DeclaredProperties.FirstOrDefault(pi => pi.Name == name &&
                                                                      pi.SetMethod != null &&
                                                                      pi.SetMethod.IsPublic &&
                                                                      pi.SetMethod.IsStatic);
            }
        }
    }
}

// 静态方法调用示例:
public static void Main(string[] args)
{
    dynamic stringType = new StaticMemberDynamicWrapper(typeof(String));
    var r = stringType.Concat("A", "B");
    Console.WriteLine(r);
}

引用类型:所有class声明的类型都是引用类型。具有以下特性:
1.引用类型是由类型对象指针,同步块索引,静态字段以及方法表组成。
2.使用new操作符来创建引用类型实例时,CLR会从托管堆上分配内存,当托管堆的内存不足时就可能造成垃圾回收;然后CLR会自动初始化引用类型实例的类型对象指针和同步块索引并将所有属性字段值设置成null或者0;最后调用构造函数来初始化属性字段值。当引用类型实例不再被使用时就会通过垃圾回收机制(没有delete操作符)自动回收。

值类型:所有struct和enum声明的类型都是值类型。具有以下特性:
1.所有值类型都是继承自System.ValueType抽象类;而System.ValueType抽象类又是隐式继承自System.Object类。
2.所有值类型都是隐式密封的,不能派生子类;但是可以为值类型实现多个接口。
3.所有值类型实例都是在内存大小为1MB的线程栈上进行空间分配;对值类型实例使用new操作符的目的是为了告诉编译器该值类型实例已经初始化了,可以直接使用了;否则CLR会自动将值类型实例初始化为0,但是如果试图访问没有显示初始化的值类型实例时,编译器会报告错误。
4.函数之间互相调用时也是在线程栈上进行操作的。以函数F1调用函数F2为例流程如下:
1>.向线程栈中依次压入F2的参数变量。
2>.向线程栈中压入F1的函数地址,这里称为返回地址。
3>.向线程栈中压入F2的栈帧并调用该函数。
4>.执行F2的业务逻辑直到遇到return指令。
5>.展开F2的栈帧并返回到线程栈中压入的返回地址。
6>.继续调用返回地址,也就是这里的F1。
7>.继续执行F1的业务逻辑直到遇到return指令。
8>.展开F1的栈帧并返回到线程栈中压入的返回地址。
9>.继续调用返回地址,也就是调用F1的上层函数。至此函数F1调用F2的执行流程完毕。
5.设计自己的值类型时,必须满足以下条件:
1>.值类型不继承自除了ValueType外其它任何类型,但是可以实现多个接口。
2>.值类型不能派生任何其它子类型。
3>.不要使用接口来更改已装箱值类型中的字段,而是应该保证这些字段是不可变的。
4>.值类型实例大小控制在16字节以内,大于16字节时就不要作为方法实参进行传递,也不要作为方法结果进行返回。
6.由于值类型没有同步块索引,所以不能使用Monitor类型的方法或者lock方法让多个线程同步对该值类型实例的访问。
7.值类型实例调用自己重载(Equals,GetHashCode,ToString)或者定义的函数时,调用对象不会进行装箱操作;值类型实例调用父类中的非重载方法(GetType,MemberwiseClone)时,调用对象会进行装箱操作来将值类型实例转换成父类实例。

装箱:值类型转换成引用类型时就会进行装箱操作。具有以下特性:
1.装箱流程如下所示:
1>.在托管堆上分配内存空间。分配的内存大小是由值类型属性成员+类型对象指针+同步块索引组成。
2>.将线程栈上值类型实例的属性成员值复制到新分配的托管堆中。
3>.返回引用类型实例地址,此时值类型就变成了引用类型。
2.应该尽量不要进行装箱操作,因为这样会在托管堆上额外分配内存空间以及容易造成垃圾回收。如果必须要进行装箱操作的话,那么最好手动的将装箱次数降到最低。

拆箱:引用类型转换成值类型时就会进行拆箱操作。具有以下特性:
1.拆箱流程如下所示:
1>.如果引用类型实例为空,此时转换时会抛出NullReferenceException异常。
2>.如果引用类型实例和值类型实例不是相同类型的话,此时转换时会抛出InvalidCastException异常。
3>.获取引用类型实例中非装箱部分属性字段的指针(地址)。
4>.将获取的属性字段值从托管堆中复制到线程栈上值类型实例关联的属性字段上。

字段布局:使用StructLayoutAttribute特性来对类型中的字段进行布局。常见的布局方式如下所示:
1.LayoutKind.Auto让CLR按照合适的方式对定义的字段进行自动排列。引用类型默认就是这种布局方式。
2.LayoutKind.Sequential让CLR按照字段定义的先后顺序进行排列。值类型默认就是这种布局方式。
3.LayoutKind.Explicit让CLR按照内存偏移量对定义的字段进行显示排列。每个字段可以使用FieldOffset来指定距离起始内存的偏移量。特别注意:在类型中一个引用类型字段和一个值类型字段具有相同的偏移值是不合法的;而值类型之间具有相同的偏移值可以用来实现c++中的union类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值