设计类型

设计类型

一、类型基础

常见问题

  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()方法返回的是类型对象指针的地址。

同步块

来自横刀天笑syncblock_thumb.png
  • 同步块机制

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

  • 同步块的概念

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

同步块索引

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

参考内容

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

基元类型

编译器直接支持的类型成为基元类型(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方法参考范例
    /// <summary>
    /// 自定义对象类型重写
    /// </summary>
    public class A : IEquatable<A>
    {

        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);
        }

    }

    /// <summary>
    /// ValueType类型比较
    /// 注意:int?这种还是ValueType类型,注意**可为空**)。
    /// </summary>
    public struct B : IEquatable<B>
    {
        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<string>,实现类型更加安全的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
    /// <summary>
    /// 适用于4.5版本或以上
    /// </summary>
    public class StaticMemberDynamicWrapper : DynamicObject
    {
        private readonly TypeInfo _typeInfo;

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

        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return this._typeInfo.DeclaredMembers.Select(p => p.Name);
        }

        /// <summary>
        /// 获取字段或者属性的值
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        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;
        }

        /// <summary>
        /// 设置属性或者字段值
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        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;
        }

        /// <summary>
        /// 调用方法
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="args"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        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;
        }

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

        /// <summary>
        /// 方法参数类型比较
        /// </summary>
        /// <param name="parameterInfos"></param>
        /// <param name="types"></param>
        /// <returns></returns>
        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;
        }

        /// <summary>
        /// 寻找属性
        /// </summary>
        /// <param name="name"></param>
        /// <param name="get"></param>
        /// <returns></returns>
        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);
        }

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

    }
    
    /// <summary>
    /// 测试
    /// </summary>
    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<string,string,string>)((c, d) => { return c + d; });
            var result3 = sampleObject.Concat("A", "B");
            Console.WriteLine(result3);
            Console.Write(result);
        }
    }

参考内容

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,412评论 1 14
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,690评论 0 11
  • 人吧可能都有一种通性,就是害怕改变,却想着以后事事顺利。每个人都是一个案例,有多少因为畏惧改变而错过太多机会。我算...
    怀抱有时阅读 274评论 0 0
  • 金陵创业十余载,豪情壮志藏胸怀。 厚积薄发待时日,春风化雨燕归来。
    庭上望月阅读 325评论 2 29