设计类型

设计类型

一、类型基础

常见问题

  1. 面向对象语言的特点?
  • 什么是面向对象语言

    面向对象语言(Object-Oriented Language)是一类以对象作为基本程序结构单位的程序设计语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。
    语言中提供了类、继承等成分,有识认性、多态性、类别性和继承性四个主要特点。

  • 面向对象语言的特点

    面向对象语言刻画客观系统较为自然,便于软件扩充与复用。有四个主要特点:

    (1)识认性,系统中的基本构件可识认为一组可识别的离散对象;
    (2)类别性,系统具有相同数据结构与行为的所有对象可组成一类;
    (3)多态性,对象具有惟一的静态类型和多个可能的动态类型;
    (4)继承性,在基本层次关系的不同类中共享数据和操作。
    其中,前三者为基础,继承是特色。四者(有时再加上动态绑定)结合使用,体现出面向对象语言的表达能力。

  1. var o = new object(); // CLR背后帮我们做了哪些事情?
    
    • 计算类型及其所有基类型(一直到System.Object)中所有的实例字段需要的字节数。注意,堆上每个对象都需要一些额外的成员,当中包含额外成员(类型对象指针(type object pointer)和同步块索引(sync block index))占用的字节数。
    • 从托管堆中分配类型要求的字节数。
    • 初始化对象的“类型对象指针”和“同步块索引”成员。
    • 非静态类初始化:调用类型的实例构造器,传递在new调用中指定的实参,一般构造函数中会自动生成代码来自动调用基类构造器。
    • 返回指向该对象的一个引用(或指针)。
  2. is和as使用存在的坑点。

  • as

    • 针对引用对象或者可为null的对象使用。

    • as不对改变对象的类型

    • 类型转换推荐写法

      // @不推荐
      if (o is Employee){
          Employee e = (Employee)o;
      }
      // @推荐,简化以上写法,优化性能
      Employee e = o as Employee;
      if(e != null){
          
      }
      
  1. 命名空间和程序集的关系?
  • 命名空间和程序集(实现类型的文件)不一定相关,同一个命名空间中的类型可能在不同程序集中实现
  1. “运行时”的相互关系(参考堆栈的内存分配
  • 线程创建时默认分配1MB的栈。

专业名词

类型对象指针

  • 指向类型对象存储的地址,假如有一个类型Person,它在堆中有一块区域存储它内部的字段和成员以及两个额外成员(类型对象指针、 同步块索引 ),类型对象的类型对象指针指向的是System.Type类型对象的地址
  • 因为Person类型在内存中相对于System.Type也是作为一个对象存在的,System.Type类型也是一个类型对象,它的类型对象指针指向本身;
  • 调用Object.GetType()方法返回的是类型对象指针的地址。

同步块

设计类型_第1张图片
来自横刀天笑syncblock_thumb.png
  • 同步块机制

    · 在.NET被加载时初始化同步块数组。
    · 每一个被分配在堆上的对象都会包含两个额外的字段,其中一个存储类型指针,而另外一个就是同步块索引,初始时被赋值为-1。
    · 当一个线程试图使用该对象进入同步时,会检查该对象的同步索引。如果索引为负数,则会在同步块数组中寻找或者新建一个同步块,并且把同步块的索引值写入该对象的同步索引中。如果该对象的同步索引不为负值,则找到该对象的同步块并且检查是否有其他线程在使用该同步块,如果有则进入等待状态,如果没有则申明使用该同步块。

  • 同步块的概念

    同步块是指.NET维护的同步块数组中的某个元素,负责提供线程同步的操作,当某个线程拥有了某个同步块时,其他线程就在试图访问该同步块时进入等待状态。

同步块索引

​ 同步索引是每个堆内对象都会分配的一个字段。

参考内容

  • 《CLR Via C#(第4版)》
  • 揭示同步块索引(上):从lock开始
  • 揭示同步块索引(中):如何获得对象的HashCode
  • 揭示同步块索引(下):总结

二、基元类型、引用类型和值类型

基元类型

编译器直接支持的类型成为基元类型(Primitive Type),能直接映射到FCL中存在的类型。

常见问题

  • System.Decimal没有响应的IL指令来处理Decimal值,无法检测溢出情况。
  • UnChecked和Checked

引用类型和值类型

常见问题

声明为值类型的前提条件

​ 优势是不作为对象在托管对上的分配。

  • 必选条件

    1. 类型具有基元类型的行为,是十分简单的类型,没有成员会修改类型的任何实例字段。
    2. 类型不需要从其他任何类型继承。
    3. 类型也不派生出其他任何类型。
  • 任一条件

    1. 类型的实例较小(16字节或更小)
    2. 类型的实例比较大(大于16字节),但不作为方法实参传递,也不从方法返回。
字段布局

​ 使用StructLayoutAttribute特性,FieldOffsetAttribute特性指出字段第一个字节举例实例起始处的偏移量。

  • 然而在.net托管环境中,CLR提供了更自由的方式来控制struct中Layout:我们可以在定义struct时,在struct上运用StructLayoutAttribute特性来控制成员的内存布局。默认情况下,struct实例中的字段在栈上的布局(Layout)顺序与声明中的顺序相同,即在struct上运用[StructLayoutAttribute(LayoutKind.Sequential)]特性,这样做的原因是结构常用于和非托管代码交互的情形。
  • 如果我们正在创建一个与非托管代码没有任何互操作的struct类型,我们很可能希望改变C#编译器的这种默认规则,因此LayoutKind除了Sequential成员之外,还有两个成员Auto和Explicit,给StructLayoutAttribute传入LayoutKind.Auto可以让CLR按照自己选择的最优方式来排列实例中的字段;传入LayoutKind.Explicit可以使字段按照我们的在字段上设定的FieldOffset来更灵活的设置字段排序方式,但这种方式也挺危险的,如果设置错误后果将会比较严重。
装箱和拆箱
  • 读懂相关IL代码,参考IL章节。
Equals和GethashCode重载
Equals
  • 重写Equals方法参考范例
    /// 
    /// 自定义对象类型重写
    /// 
    public class A : IEquatable
    {

        public string Id { get; set; }

        // 实现IEquatable接口
        public bool Equals(A other)
        {
            // 为空,肯定不相等
            if (ReferenceEquals(null, other)) return false;
            // 同一对象,必然相等
            if (ReferenceEquals(this, other)) return true;
            // 比较各个字段
            if (!string.Equals(this.Id, other.Id, StringComparison.InvariantCulture))
            {
                return false;
            }
            return true;
        }

        public override bool Equals(object obj)
        {
            //this非空,obj如果为空,则返回false
            if (ReferenceEquals(null, obj)) return false;
            //如果为同一对象,必然相等
            if (ReferenceEquals(this, obj)) return true;
            //如果类型不同,则必然不相等
            if (obj.GetType() != this.GetType()) return false;
            //调用强类型对比
            return Equals((A)obj);
        }

        public override int GetHashCode()
        {
            return this.Id == null ? 0 : StringComparer.InvariantCulture.GetHashCode(this.Id);
        }

        // 重写==号
        public static bool operator ==(A left, A right)
        {
            return Equals(left, right);
        }

        // 重载!=号
        public static bool operator !=(A left, A right)
        {
            return !Equals(left,right);
        }

    }

    /// 
    /// ValueType类型比较
    /// 注意:int?这种还是ValueType类型,注意**可为空**)。
    /// 
    public struct B : IEquatable
    {
        private string _id;

        public B(string id)
        {
            this._id = id;
        }

        public bool Equals(B other)
        {
            // 比较字段是否相等
            return string.Equals(this._id,other._id,StringComparison.InvariantCulture);
        }

        public override bool Equals(object obj)
        {
            // 为空不相等
            if (ReferenceEquals(null, obj)) return false;
            return obj is B && Equals((B)obj);
        }

        public override int GetHashCode()
        {
            return (this._id != null) ? StringComparer.InvariantCulture.GetHashCode(_id) : 0;
        }

        public static bool operator ==(B left,B right)
        {
            if (left == null && right == null)
                return true;
            if (left == null)
                return right.Equals(left);
            return left.Equals(right);
        }

        public static bool operator !=(B left, B right)
        {
            if (left == null && right == null)
                return false;
            if (left == null)
                return !right.Equals(left);
            return !left.Equals(right);
        }
    }

  • 定义自己类型,重写Equals要符合相等性的4个特征:
    • Equals必须自反,x.Equals(x)肯定返回true。
    • Equals必须对称,x.Equals(y)和y.Equals(x)返回相同的值。
    • Equals必须可传递,x.Equals(y)和y.Equals(z)返回true,则x.Equals(z)肯定也返回true。
    • Equals必须一致,比较的两个值不变,Equals返回值也不能变。
  • 实现IEquatable,实现类型更加安全的Equals方法。
  • 重写==和!=等操作符方法。
HashCode
  • 重写hashcode的时候,遵守以下的规则:
    • 计算算法要提供良好的随机分布,使哈希表获得最佳性能和减少hash冲突的可能性。
    • 可在计算中调用基类的GetHashCode方法,并包含它的返回值。但是一般不要调用Object和ValueType的GetHashCode方法。
    • 算法至少使用一个实例的字段。
    • 理想情况下,算法使用的字段应该不可变(immutable),换句话说,字段在构造初始化后,在对象生存期“永不言变”。
    • 算法执行速度尽量快。
    • 包含相同值的两个对象应该返回相同的哈希码。
dynamic(比较重要,后续补充完整)

Visual C# 2010 引入了一个新类型 dynamic。 该类型是一种静态类型,但类型为 dynamic 的对象会跳过静态类 型检查。 大多数情况下,该对象就像具有类型 object 一样。 在编译时,将假定类型化为 dynamic 的元素支持任何操作。 因此,您不必考虑对象是从 COM API、从动态语言(例如 IronPython)、从 HTML 文档对象模型 (DOM)、从反射还是从程序中的其他位置获取自己的值。 但是,如果代码无效,则在运行时会捕获到错误。

  • 应用场景
    • 动态类型转换(需要考虑性能)
    • 动态语言运行时(DLR,例如 IronPython 和 IronRuby 等动态编程语言的实现)
    • COM 互操作
参考范例(继续改进)
  • 覆写DynamicObject
    /// 
    /// 适用于4.5版本或以上
    /// 
    public class StaticMemberDynamicWrapper : DynamicObject
    {
        private readonly TypeInfo _typeInfo;

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

        public override IEnumerable GetDynamicMemberNames()
        {
            return this._typeInfo.DeclaredMembers.Select(p => p.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, true);
            if (prop != null)
            {
                prop.SetValue(null,value,null);
                return true;
            }
            return false;
        }

        /// 
        /// 调用方法
        /// 
        /// 
        /// 
        /// 
        /// 
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            MethodInfo methodInfo = FindMethodInfo(binder.Name,args.Select(p=> p.GetType()).ToArray());
            if (methodInfo == null)
            {
                result = null;
                return false;
            }
            result = methodInfo.Invoke(null, args);
            return true;
        }

        /// 
        /// 寻找方法
        /// 
        /// 
        /// 
        /// 
        private MethodInfo FindMethodInfo(string name, Type[] types)
        {
            return _typeInfo.DeclaredMethods.FirstOrDefault(p => p.Name == name && p.IsPublic && p.IsStatic && ParametersMatch(p.GetParameters(), types));
            
        }

        /// 
        /// 方法参数类型比较
        /// 
        /// 
        /// 
        /// 
        private bool ParametersMatch(ParameterInfo[] parameterInfos, Type[] types)
        {
            if (parameterInfos == null || parameterInfos.Length <= 0)
            {
                return false;
            }
            for (int i = 0; i < types.Length; i++)
            {
                if (parameterInfos[i].ParameterType != types[i])
                    return false;
            }
            return true;
        }

        /// 
        /// 寻找属性
        /// 
        /// 
        /// 
        /// 
        private PropertyInfo FindProperty(string name, bool get)
        {
            if (get)
            {
                return _typeInfo.DeclaredProperties.FirstOrDefault(p => p.Name == name && p.GetMethod != null && p.GetMethod.IsPublic && p.GetMethod.IsStatic);
            }
            return _typeInfo.DeclaredProperties.FirstOrDefault(p => p.Name == name && p.SetMethod != null && p.SetMethod.IsPublic && p.SetMethod.IsStatic);
        }

        /// 
        /// 寻找字段
        /// 
        /// 
        /// 
        private FieldInfo FindField(string name)
        {
            return this._typeInfo.DeclaredFields.FirstOrDefault(p => p.IsPublic && p.IsStatic && p.Name.Equals(name));
        }

    }
    
    /// 
    /// 测试
    /// 
    public class DyamicObjectTest
    {
        public void Run()
        {
            dynamic stringType = new StaticMemberDynamicWrapper(typeof(string));
            var result = stringType.Concat("A", "B");
            // 运行报错
            //dynamic stringType2 = "";
            //var result2 = stringType2.Concat("B", "C");
            //Console.Write(result2);
            dynamic sampleObject = new ExpandoObject();
            sampleObject.Concat = (Func)((c, d) => { return c + d; });
            var result3 = sampleObject.Concat("A", "B");
            Console.WriteLine(result3);
            Console.Write(result);
        }
    }

参考内容

  • C#如何重写Equals
  • ironpython2
  • 动态语言运行时概述
  • 通过使用 Visual C# 2010 功能访问 Office 互操作对象
  • 创建和使用动态对象
  • DLR
  • IDynamicMetaObjectProvider

你可能感兴趣的:(设计类型)