1 类型基础
面试出现频率:基本上肯定出现
重要程度:10/10,身家性命般重要。通常这也是各种招聘工作的第一个要求,即“熟悉C#”的一部分。连这部分都不清楚的人,可以说根本不知道自己每天都在干什么。我们天天使用C#写程序,但如果连C#基础的东西都不懂,怎么证明你“熟悉C#”呢?怎么让人觉的你对C#有兴趣呢?
很多人去面试一发现面试官开始问基础题,就十分不爽,被淘汰了之后,还写博客说面试官垃圾,怎么不问问项目经历,哥可是做过不少项目的。殊不知,面试官知道你做过那些项目,但通常来说,如果那些项目不是牛逼透顶的级别(例如你参与了淘宝双11导致数据库并发问题的改进,或者AlphaGo的算法设计),或者正好是面试官所在公司需要的类型,则这并不是什么很厉害的事情,是个程序员就有几个项目在身,“做过不少项目”的牛逼程度,差不多等于“活过20几年”(我都活了20几年了,我牛逼么?)。每个人都有的东西,有什么好问的,问你了你能确定你能答得比别人好么?但是如果你不能答出什么是装箱,你会引发面试官以下的猜想:
- 这人连最基础的东西都不知道,还写了熟悉C#,他还写了熟悉XX,熟悉YY,看来他对那些东西可能也就了解皮毛。呵呵他还说他懂设计模式
- 这人连最基础的东西都不知道,说明他平常不看书。连书都不看,对技术肯定没有什么兴趣
- 这人写了他做过20个项目,但在我看来,他们大同小异,和做过1个项目也没区别
- 这人还写了他有管理经验,自己都这样,小弟的水平。。。
最重要的是,如果你装箱都不知道,面试官后面的N个连环问题马上胎死腹中,他可能会一脸尴尬,因为“我只是用这个问题当破冰的啊,你怎么已经倒地了”,甚至不知道该问你啥,你才知道。C#话题就此终结,和善点的面试官,可能会问问你在简历上写的其他东西。但无论如何,你的价值已经狂跌了不止一个档次。
在老外看来,这部分内容更为重要。很多老外,尤其是从优越国家来的那帮人,认为人找工作肯定是为了兴趣,钱只不过是顺带获得的。他们根本不知道这世界上还有人会考虑一个自身毫无兴趣,仅仅是工资高的工作。如果他们发现,你连装箱都不知道是什么,他们会觉得你不熟悉C#,对C#一点兴趣都没有,直接把你请出面试室,尽管你可能已经用C#写了几十个工程,手下可能已经有了几个小弟。也许你会央求面试官转换一个话题,例如问问设计模式,但个人认为,基础有问题的人,即使知道设计模式,做过很多项目,他写出来的asp.net代码可能是一坨屎的几率要远远高于基础没问题,但完全不懂asp.net的人。这也是为什么很多老外的C#书籍前几章的内容好像都是些“毫无意义的”,“莫名其妙的”东西。CLR via C#更是其中的战斗机,你完全不用看这本书,也能写出一个后台用asp.net MVC,前端html+css+jquery的ERP系统出来,前后端使用ajax通讯,后端连数据库,用sql查数据,做CRUD。但是你不能凭借这个找到一个工资高的工作,因为会干这个的人实在是太多了,以至于不够值钱。如果你觉得这已经是了不起的成就,那么你这一生也就停留在这里了。如果你还想挣得更多,那么你就得会别人不会或者嗤之以鼻的东西。
而工资高的工作,或对性能有很高的要求,或如果你写的代码太差,那真的会出大事,所以不能请基础差的人(当然好公司会有层层环境把关,但如果你的代码老出问题,你的水平弱于公司平均水平太多,他们也不会请你)。小公司尤其是外包,或者没什么名气的公司写的产品,本身也没有多少人用,崩溃了不会死人,所以代码垃圾一点无妨,只要能按时完成任务就得。
很多人反感基础题,一个很大的原因在于,问问题的人不会问。如果问法是考定义,比如问“值类型与引用类型有何区别?” 这种问题的答案一查都找得到,也没有什么意义。较好的问法是,把概念问题融入到情景之中,或者构造一个连环问题。例如我遇到过的一个问题:你何时会考虑使用一个结构体?我觉得一个不错的答案是”当这个对象所有的属性都是值类型时,例如刻画N维坐标系上的一个点”。如果面试者是如此作答,那么你可以继续问“可以用类型么?“这个时候,实际上还是在问你值类型与引用类型有何区别,但相比直接问就自然很多。这个问题并不是概念题,而是每天工作都会要遇到的。你总需要建立自定义的对象吧,那你就得从类型,结构,接口...中选择一个。
需要理解的程度:熟悉值类型和引用类型的区别,以及它们之间是可以转换的(虽然这种转换基本上是一定要避免的)。对栈和堆上内存的活动有着清醒的认识。
参考资料:
- http://www.cnblogs.com/anding/p/5229756.html
- CLR via C#
1.1 公共类型系统(CTS)
公共类型系统(CTS)是用来描述IL的,它规定了IL能做什么,能定义什么样的变量,类中允许拥有什么成员等等。如果你写了一个不遵循CTS的语言(以及一个编译器),那么你的语言不能被看成是.NET平台的语言,编译出来的中间代码(如果有的话)不是IL。CTS和IL是所有.NET语言的爸爸。
C#的数据类型可以分为值类型和引用类型。这是因为,CTS爸爸规定数据类型可以分为值类型和引用类型,而且C#实现了这部分功能。你可以开发一个遵循CTS的语言,但不实现任何值类型。
所有类型都从System.Object派生,接口是一个特例。下面是一些主要的System.Object提供的方法:
- Equals(obj):虚方法。如果两个对象具有相同的引用就返回true。
- 注意,尽管引用类型可能包含许多成员,比较引用类型时,仅仅考虑栈上的两个对象是否指向堆上相同的对象,而不会逐个成员比较,所以对于引用类型,不需要重写该方法。
- System.ValueType(值类型)重写了该方法,使得方法不比较对象指针是否指向同一个对象,而是仅仅比较值是否相等。此时,如果值类型包含很多成员(例如结构),会使用反射逐个成员比较。为了避开反射造成的性能损失,你必须重写该方法,你只需要在其中遍历所有结构的属性,并一一进行比较即可。如果你自定义的结构的相等逻辑不要求所有的属性相等才意味着相等,而只是部分属性相等就意味着相等时,你也需要重写该方法。
- 值得注意的是,虽然字符串是引用类型,它也重写了该方法,其行为和值类型一样。
- Equals(obj1, obj2):静态方法,若两个输入变量均为null则返回true。若仅有一个是null则返回false。若都不是null则调用obj1.Equals(obj2)。故该方法无需重写,也不是虚方法。
- GetHashCode:在FCL中,任何对象的任何实例都对应一个哈希码。为此,System.Object的虚方法GetHashCode能获取任意对象的哈希码。如果你定义的一个类型重写了Equal方法,那么还应重写GetHashCode方法。事实上如果你没有这么做的话,编译器会报告一条警告消息:重写了Equal但不重写GetHashCode。CLR via C#中说,一般都要重写Object的GetHashCode方法,因为它的算法性能不高。但我对这一部分没有深入研究。
- ToString:虚方法。返回类型的完整名称(this.GetType().FullName)。重写它的可能性很大,例如你希望ToString遍历对象的所有属性,打印出它所有属性的值。
- GetType:返回对象的类型对象指针指向的类型对象。
- Finalize:在GC决定回收这个对象之后,会调用这个方法。如果要做一些额外的例如回收对象的非托管属性或对象,应当重写这个方法。只有在存在非托管对象时才需要这么做。在垃圾回收中会详细介绍。
1.2 New操作符
CLR要求所有对象都用new操作符来创建。对于值类型,你可以直接赋值,这相当于隐式的调用了new操作符。new操作符所做的事情有:
- 计算类型及其所有基类型中定义的实例字段需要的字节数,另外,如果是引用类型,还需要预留空间给”类型对象指针“和”同步块索引“。如果发现栈或者堆上的空间不足,就引发OutOfMemory异常,并激发一次垃圾回收。
- 如果是引用类型,从堆上分配第一步算出来的字节数。
- 初始化”类型对象指针“和”同步块索引“。令”类型对象指针“指向堆上该类型的类型对象。如果类型对象不存在,则创建一个。并且如果类型有静态成员,则初始化它们,如果类型有静态构造函数,调用静态构造函数,初始化或者修改(因为静态构造函数在初始化静态成员之后进行,所以可能会造成修改)类中的静态成员的值。如果类型对象已经存在,则不会再次调用静态构造函数。
- 调用类型的实例初始化器,初始化类型的非静态成员。
例如下面的代码中,C#首先将a初始化为5,然后再修改成10。
1 class SomeType 2 { 3 private static int a = 5; 4 5 static SomeType() 6 { 7 a = 10; 8 } 9 }