(转).net面试题(老赵)

时间:2024-10-29 13:33:32

转自:http://www.cnblogs.com/chenxiaoran/archive/2012/05/27/2519988.html

1.什么是CLR

公共语言运行时(Comman language Runtime),是一个可由多种编程语言使用的“运行时”。CLR的核心功能:程序集加载,异常处理,线程同步,内存管理等可由CLR

的所有语言使用.

2.什么是IL

Intermediate language

中间语言,.net程序在经过编译后就成为IL代码。运行时CLR将IL语言编译成CPU能识别的CRU指令。IL也可以叫做托管代码,IL可以访问CLR所提供的所有功能。

3.什么是JIT,它是如何工作的?

即时编译器,由CLR调用,负责将IL语言编译成本地CPU指令。

工作原理:

当程序被第一次调用的时候,CLR会指向包含在CLR内部定义的特殊函数,这个函数就是JITCompiler。JITComliler负责将IL代码编译成本地指令。

JITCompiler知道实际调用了哪个方法,以及该方法是哪些类型定义的。JITCompiler会在定义该类型的程序集的元数据中查找该方法的IL代码。

并将IL编译成本地CPU指令。编译好的结果被放在一个内存块中,JITCompiler返回CLR为类型定义的内部数据结构。找到被调用方法对应的那条记录,

并修改最初对于JITCompiler的引用。让其指向内存块中被调用方法刚刚编译好的CPU指令地址,最后执行被调用方法的CPU指令。

4.GC是什么,简述一下GC的工作方式?

  垃圾回收(garbage collection)

  Dot Net的垃圾回收可以分为两个步骤,第一步进行“标记”,垃圾回收器假设所有的对象都是垃圾,然后开始遍历每一个“根”(根包含指向引用类型对象的一个指针,值类型对象永远不会被认为是一个根),如果发现一个根引用了一个对象(非NULL),就对对象进行标记。没有被标记的对象被认为是垃圾。第二个阶段就是“压缩”,其实就是将后面的对象移动到已经成为垃圾的对象位置,使得原来的托管堆更为紧凑。从而释放了托管堆。

GC类中的方法影响何时对对象进行垃圾回收以及何时释放对象所分配的资源。此类中的属性提供以下信息:系统可用内存总量、分配给对象的内存的周期类别(代)。

GC跟踪并回收托管内存中分配的对象。垃圾回收器定期执行垃圾回收以回收分配给没有有效引用的对象的内存。当使用可用内存不能满足内存请求时,垃圾回收会自动进行。或者,应用程序可以使用 Collect 方法强制进行垃圾回收。

垃圾回收由以下步骤组成:

GC搜索托管代码中引用的托管对象。

GC尝试完成没有被引用的对象。

GC释放没有被引用的对象并回收它们的内存。

在回收期间,如果GC在托管代码中找到对某对象的一个或多个引用,则不会释放该对象。然而,GC不识别非托管代码中对对象的引用,因此,除非明确禁止,否则它有可能释放非托管代码中以独占方式使用的对象。KeepAlive 方法提供一种机制,该机制可防止垃圾回收器回收在非托管代码中仍使用的对象。

5..NET程序运行过程中,什么是堆,什么是栈?什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗?结构对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的?

.NET进程被创建时就会有一个堆随之被创建, 用来保存该进程在运行中需要使用的对象/数据; 当一个线程被创建时, 会有一个栈被创建,用来保存方法调用参数, 局部变量等轻量型数据.

当一个类里面包含一个结构体类型的变量时, 该结构体类型会被分配在堆上.

>泛型的作用是什么?它有什么优势?它对性能有影响吗?它在执行时的行为是什么?.NET BCL中有哪些泛型类型?举例说明平时编程中您定义的泛型类型

泛型的作用是什么?

   泛型的作用在于“算法的重用”。(这点其实很好理解,原来的ArrayList只能接受Object,现在通过List可以接受任何类型,也就是说ArrayList的方法都被各个类型重用了。但是Dot Net的泛型有个比较制肘地方,就是你很难对数值类型(值类型)进行算法抽象,因为这牵涉到运算符重载的问题,同时Dot Net的泛型的类型参数也不能约束成一个基元值类型(如int、double、float) 。)

   它有什么优势?

   第一:源代码保护。(如果你知道C++模板对泛型的实现机制,就会知道C++在编译的时候根据对泛型的调用,自动“内联”了一个实现,这样泛型的内容就暴露了,尔DotNet的实现方式就不同了,泛型类和方法会被编译成IL,在执行的时候由JIT负责将IL变化为指定类型参数的本地代码,从而保护了源代码)

   第二:类型安全。(这点是最显而易见的,抛弃了使用ArrayList时各种丑陋的强制类型转换)

   第三:更清晰地代码。因为没有了强制类型转换,所以代码自然显得更清晰,但是使用泛型时候带来的<>有时候确实也会让人搞糊涂,幸好泛型方法可以用类型推断或者using语句来进一步简化写法。

   第四:更好的性能,因为值类型可以避免装箱和拆箱所带来的损耗(垃圾回收的次数也会减少)。(这点正是泛型神奇的地方,开发历史上抽象能力的上升往往意味着性能的下降,但是泛型却不是!泛型抽象了算法,但是C++和DotNet对泛型的实现能够让性能无损,并且更快。Java的擦除法泛型就没有这种性能上的好处。)

   它对性能有影响吗?

  对性能有积极的影响,因为值类型可以避免装箱和拆箱所带来的负面影响,避免了垃圾回收,使得性能显著提高。但是对引用类型这种影响就不明显了。但是需要注意的是首次为一个特定数据类型调用方法时,CLR都会为这个方法生成本地代码。这会增大应用程序的工作集大小,从而影响性能。

   它在执行时的行为是什么?

   使用泛型类型参数的一个方法在进行JIT编译时,CLR获取IL,用指定的类型实参进行替换,然后创建本地代码。需要特别注意的是引用类型是共享代码的,而对值类型就会为每一种生成独立的一份类型代码。但是需要指出的是引用类型的这种代码共享并不会造成封闭类型只执行一次构造函数(就算是静态构造函数也是这样的)。

   .NET BCL中有哪些泛型类型?

 List、Dictionary、Queue、Stack、SortedList和SortedDictionary、LinkedList等等。

  举例说明平时编程中您定义的泛型类型。

泛型的出现会替换原来一部分使用多态的地方从而提高性能和带来更好的编译时检查,这样就不需要在子类和超类(接口)间频繁转换了。比如你要根据情况打出各种报表,那么先把报表类定义成泛型类从而可以共享报表一系列的算法。

异常的作用是什么?.NET BCL中有哪些常见的异常?在代码中您是如何捕获/处理异常的?在“catch (ex)”中,“throw”和“throw ex”有什么区别?您会如何设计异常的结构,什么情况下您会抛出异常?

异常的作用是什么?

  异常用于处理系统级或者应用程序级的错误状态。这就会引发另外几个问题,异常相比原来使用的返回错误代码的优点在哪里?异常处理是一种结构化的处理过程,个人认为他最大的优点就在于将“成功场景”剥离出来,使得代码更加清晰自然。但是异常处理相对于返回错误码有一个缺点,那就是他会失去发生异常的位置。不过异常本身提供了很多帮助调试问题的工具,一般都带有栈跟踪,这样位置的问题就得到一定程度的解决。还有就是IF和异常之间的选择,我记得以前有人讨论过在各种分支下是使用异常来处理各种“失败场景”的分支还是使用IF或者SWITCH来处理呢?这其实是一个假问题,因为异常和错误是有概念上的不同的,这里的错误是指有违“主成功场景”的“异常场景”,尔异常是指当程序不能完成其名字所表示功能时的错误。所以需要强调不要使用异常来区分各种失败场景,异常压根就不是用来干这件事情的!

  .NET BCL中有哪些常见的异常?

  随便说几个,最著名的恐怕就是那句像绕口令一样的“未将对象引用设置到对象实例”了,还有那些基本一出现整个应用程序就被判死刑的“堆栈溢出”、“内存无法分配”异常了。

  在代码中您是如何捕获/处理异常的?

  这道题的回答可以体现你是什么“级别”的程序员,这个级别倒不是说水平的高低,是指经常写哪一类的程序,如果对异常的捕获比较“激进”(经常捕获异常)那么这个人应该是一个应用级的程序员。如果对捕获异常比较谨慎那么应该是框架级别的程序员,这些人经常写给别人使用的代码,如果无故的使用异常处理来越俎代庖,那后果很严重了,这里说一个我经历的事,刚毕业的时候我和同事做一个WEB的项目,项目里用第三方的Grid,那个Grid在发生异常的时候会自己报一个错误,你知道我们有多傻眼了吧,我们需要的是我们来抓住异常,然后报出一句对用户友好的错误,但是那个控件却干了这么个蠢事。

  我觉得普通程序员用的最多的CATCH就是抓住数据的异常,然后回滚数据库来事务处理。这是一个典型的场景,因为你明确并且能够很好的恢复状态。

  在“catch (ex)”中,“throw”和“throw ex”有什么区别?

  throw 重新抛出异常但是不破坏异常发生的调用栈尔“throw ex”会重置调用栈这样捕获异常的人会以为代码出错在这里。

  您会如何设计异常的结构,什么情况下您会抛出异常?

  首先我会尽量的使用系统定义的那些异常,如果我需要处理某一特定类别的异常,而且处理方式和通常处理方式不同那么就考虑自定义异常,还有如果需要调用方用一种统一的方式来处理异常那么自定义异常就是一个好的选择。结构的话当然基类是Sysytem.Exception,尽量使用扁平化异常的层次。可以考虑用泛型类来定义异常。  

  我写的代码不能完成名字所说明的功能,那么我就会抛出异常。

> List<T>T[]的区别是什么,平时你如何进行选择?Dictionary<TKey, TValue>是做什么的?.NET BCL中还有哪些常用的容器?它们分别是如何实现的(哪种数据结构)?分别是适用于哪些场景?

List和T[]的区别明显有本质的区别,List 是动态分配内存的链表,元素在内存中是分散存放的,因此可动态增减元素,使用比较灵活但元素访问的效率不高,数组元素在内存中是连续存放的,因此使用下标访问时效率极高,但因长度固定使用起来不够灵活

T[] 继承自Array, 而List<T>仅仅是对T[]的封装; 相比于T[], List<T>的size是动态变化的.

Dictionary<TKey, TValue>可以用来存储键/值对.其他的比如HashTable, SortedList等.

抽象类和接口有什么区别?使用时有什么需要注意的吗?如何选择是定义一个完全抽象的抽象类,还是接口?什么是接口的显式实现?为什么说它很重要?

抽象类定义了一个类及其子类是什么, 而接口更多的表现出一个类可以做什么.

当一个类实现连个不同的接口, 而这两个接口中包含一些相同签名的方法时需要用到显示实现.

字符串是引用类型类型还是结构类型?它和普通的引用类型相比有什么特别的地方吗?使用字符串时有什么需要注意的地方?为什么说StringBuilder比较高效?在连接多个字符串时,它无论何时都比直接相加更高效吗?

String是引用类型, 其特殊之处在于一个string是不可变的, 当我们对两个string使用连接操作时, 会生成一个新的string 对象, 而原来的两个string保持不变.

在和native代码做interop时, 对于传出参数 (char* outStr), 应当选择使用stringbuilder而非string.

StringBuilder内部维护着一个char[] 数组, 在做连接字符的操作时会动态增加其大小, 但是, 当原有的数组不够用时, StringBuilder会重新创建一个新的char[]数组, 值得注意的是原来的数组不会被抛弃, 新创建的数组只会用作存储新添加的字符.

如果说StringBuilder有”不高效”的话,应该就是在原有数组空间用尽的情况下吧.

如何高效地进行数组复制?二维数组数组的数组有什么区别?在使用双重循环遍历一个二维数组时,如何选择内外层的遍历顺序?

不清楚, 因为数组的元素在内存中的分布是连续的, 我能想到的方法是直接使用内存拷贝API.

二维数组是二维的, 数组的数组是一维的.

根据内存局部性原理, CPU在读取二维数组的第一个元素是, 第一行的数据也会被一起读入cache, 所以应当先遍历行,随后遍历列.

什么是.NET?什么是CLI?什么是CLRIL是什么?JIT是什么,它是如何工作的?GC是什么,简述一下GC的工作方式?

CLI是规范; CLR是对CLI的实现; .NET是基于CLR构建的一套框架;

开发人员需要通过IL与CLR进行交流, 虽然IL本身支持一些面向对象的概念, 但是对于开发人员来讲还是过于复杂低效, 于是C#应运而生, 程序员只需编写C#代码, csc编译器会将其翻译成IL;

虽然CLR理解IL, 但是CPU只认识二进制指令, 所以CLR需要JIT的帮助, 将IL翻译成CPU指令. JIT按需工作, 当一个.NET方法即将被执行时, JIT会介入, 把该方法(IL指令) 编译成CPU指令, 并保存以供重用.

GC被用来回收当前进程中已无人使用的垃圾对象; GC会被某些事件触发(比如, 0代对象满, 内存压力大, appdomain被卸载), 随后遍历GC堆上的对象, 并通过”该对象是否被root直接/间接引用 (更进一步, “一个对象是否可以被回收”还会依赖于F-Reachable Queue 和GC Handle Table”)来判断一个对象是否需要被回收. 具体细节还这真不是半个小时能讲完的J

类(class)和结构(struct)的区别是什么?它们对性能有影响吗?.NET BCL里有哪些是类(结构),为什么它们不是结构(类)?在自定义类型时,您如何选择是类还是结构?

简单说来, 类是引用类型, 结构是值类型;

值类型对象直接分配在当前线程的栈上, 引用类型对象位于GC堆上, 所以值类型对象无法在多个方法中传递, 而引用类型会承担更多的任务, 比如, 用于线程同步 (Monitor.Enter(…))当一个引用类型的对象被创建时, 会需要4+4=8个byte的额外空间开销(32bit OS), 同时过度的使用引用类型对象会增加GC堆的压力, 频繁的GC对程序的性能还是有一些影响的.

BCL中的Byte类型是一个结构体, 至于为什么它不是一个类, 我觉得可能是设计者认为Byte不应该被继承, 或者8个byte的额外开销无法承受吧.

什么时候使用值类型/引用类型还是应当具体情况具体分析, 个人倾向引用类型, 因为这样在设计的时候会少费些脑子, 不过能用sealed就sealed, 在方法调用时开销会小一些.

.NET程序运行过程中,什么是堆,什么是栈?什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗?“结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗?

.NET进程被创建时就会有一个堆随之被创建, 用来保存该进程在运行中需要使用的对象/数据; 当一个线程被创建时, 会有一个栈被创建,用来保存方法调用参数, 局部变量等轻量型数据.

当一个类里面包含一个结构体类型的变量时, 该结构体类型会被分配在堆上. (不知道有什么需要注意的…)

泛型的作用是什么?它有什么优势?它对性能有影响吗?它在执行时的行为是什么?.NET BCL中有哪些泛型类型?举例说明平时编程中您定义的泛型类型.

泛型有利于算法重用.

.NET进程地址空间中, 对象和类型是分开存放的, 当我们实例化一个泛型的时候 (比如List<int> list = new List<int>();), 会有一个新的类型对象被创建(该对象并不位于GC堆上), 当我们在使用这个实例化泛型去创建新的对象时, 才会有一个对象(GC堆上)被创建. 所以性能上会有些许的损失. 当我们使用一个值类型作为参数,去调用一个接收引用类型参数的方法是, 会有装箱发生, 这时我们可以考虑实现一个泛型, 并在运行时确定方法的参数类型.

异常的作用是什么?.NET BCL中有哪些常见的异常?在代码中您是如何捕获/处理异常的?在“catch (ex)”中,“throw”“throw ex”有什么区别?您会如何设计异常的结构,什么情况下您会抛出异常?

呃, 异常可以通知我们程序出错, 比如ArgumentException, NullReferenceException…

异常的发生会导致一次stack walk, 去寻找对应的exception handler, 在这个过程中, stack trace的信息会被一层层的收集, throw ex会清空之前收集的stack trace的信息, 相当于抛出了一个新的异常, 而throw不会, 所以throw ex不利于找出问题所在.

不同的layer应该catch不同exception, 最上层处理最general的exception, 底层去处理一些detail的exception.

> List<T>T[]的区别是什么,平时你如何进行选择?Dictionary<TKey, TValue>是做什么的?.NET BCL中还有哪些常用的容器?它们分别是如何实现的(哪种数据结构)?分别是适用于哪些场景?

T[] 继承自Array, 而List<T>仅仅是对T[]的封装; 相比于T[], List<T>的size是动态变化的.

Dictionary<TKey, TValue>可以用来存储键/值对.其他的比如HashTable, SortedList等.

抽象类和接口有什么区别?使用时有什么需要注意的吗?如何选择是定义一个完全抽象的抽象类,还是接口?什么是接口的显式实现?为什么说它很重要?

抽象类定义了一个类及其子类是什么, 而接口更多的表现出一个类可以做什么.

当一个类实现连个不同的接口, 而这两个接口中包含一些相同签名的方法时需要用到显示实现.

字符串是引用类型类型还是结构类型?它和普通的引用类型相比有什么特别的地方吗?使用字符串时有什么需要注意的地方?为什么说StringBuilder比较高效?在连接多个字符串时,它无论何时都比直接相加更高效吗?

String是引用类型, 其特殊之处在于一个string是不可变的, 当我们对两个string使用连接操作时, 会生成一个新的string 对象, 而原来的两个string保持不变.

在和native代码做interop时, 对于传出参数 (char* outStr), 应当选择使用stringbuilder而非string.

StringBuilder内部维护着一个char[] 数组, 在做连接字符的操作时会动态增加其大小, 但是, 当原有的数组不够用时, StringBuilder会重新创建一个新的char[]数组, 值得注意的是原来的数组不会被抛弃, 新创建的数组只会用作存储新添加的字符.

如果说StringBuilder有”不高效”的话,应该就是在原有数组空间用尽的情况下吧.

如何高效地进行数组复制?二维数组数组的数组有什么区别?在使用双重循环遍历一个二维数组时,如何选择内外层的遍历顺序?

不清楚, 因为数组的元素在内存中的分布是连续的, 我能想到的方法是直接使用内存拷贝API.

二维数组是二维的, 数组的数组是一维的.

根据内存局部性原理, CPU在读取二维数组的第一个元素是, 第一行的数据也会被一起读入cache, 所以应当先遍历行,随后遍历列.

什么是元编程,.NET有哪些元编程的手段和场景?什么是反射?能否举一些反射的常用场景?有人说反射性能较差,您怎么看待这个问题?有什么办法可以提高反射的性能吗

对元编程了解十分有限, .NET的CodeDom(或者使用reflection emit动态创建类型)应当是其中的一个场景.

反射缺点主要是:无法提供编译时的类型安全性,会在一定程度上面影响性能。因为它要扫描元数据,并且反射调用方法的时候,首先CLR要把方法使用的实参打包成一个数组,然后在内部放射又要解包这个数据放到线程栈上面去.

如果要使用一个程序来动态发现构造类型实例,并调用它的方法和属性:

可以让类型从一个编辑时已知的基类型派生,在运行时,构造派生类的一个实例,将它的引用放到这个基类型的变量中,然后调用基类型的虚方法。当然这个虚方法在这个类型里面被重写了。

或者使用接口也可以。推荐使用接口.这样做可以提高性能.

委托是什么?匿名方法是什么?在C# 3.0中,Lambda表达式是什么?扩展方法是什么?LINQ是什么?您觉得C# 3.0中还有哪些重要的特性,它们带来了什么优势?BCL中哪些类库和这些特性有关?您平时最常用哪些

委托是什么:

MSDN:委托是一种引用方法的类型,看不懂,我的理解就是委托就是对于一组方法的抽象,哪里有那么复杂。