DOTNET技术面试题及其参考答案(仅供应聘者参考)

来源:互联网 发布:数学教材 知乎 编辑:程序博客网 时间:2024/06/05 13:34
 什么是.NET?什么是CLI?什么是CLR?IL是什么?JIT是什么,它​​是如何工作的?GC是什么,简述一下GC的工作方式?
      什么 是.NET

      Dot Net是微软在2000提出的一整套平台,包括底层操作系统:Windows;辅助产品:Dot Net企业服务器;Mcirosoft XML Web服务:.Net My Services;开发平台:Dot Net框架,集成的开发环境:      Visual Studio 。以上几个部分组成。

  底层操作系统:当然是Windows 对全线的操作系统都提供XML Web服务支持。服务器操作系统2003开始,个人操作系统从Vista开始集成.Net Framework。并且操作系统原始的API也开始用Dot Net的API隔离,其原始构想相当庞大而激进,这在Vista的开发中显得尤为明显,后来微软砍掉了一些原本的准备在Vista上发布的特性,但是Vista也已经表现的和前任大不相同,当然其中的是非曲折又是另一个话题了。总之为了Dot Net计划微软在操作系统上投入的心力,可以非常明确的看出微软对Dot Net计划的重视。

  辅助产品:大家比较熟悉的就是SQL Server了吧,也有了脱胎换骨式 ​​的变化,首先是那个集成了原来企业管理器,查询分析器,跟踪器的大一统管理工具

    Microsoft XML Web服务:都是些微软提供的XML Web服务,有些收费有些免费。(XML Web服务当年着实是一个“显学”不过后来的发展并没有如微软预期的那样成功 )

  开发平台(Dot Net框架):包含通用语言运行时(CLR)和Dot Net框架类库(FCL)两个部分。他们提供了一致的编程模型,简化的编程方式,可靠的版本机制(用全局程序集缓存GAC来避免DLL Hell),轻便的部署管理(程序集自带的元数据可以避免ini文件和注册表) ,广泛的平台支持(只要这台机器兼容标准下的CLR和FCL就可以部署,当然运行的时候IL会变成本机代码),无缝的语言集成(JAVA跨平台,Dot Net跨语言这是那个时候论战经常看到的观点 ),自动化的内存管理(垃圾收集),类型安全(CLR会阻止利用缓冲区溢出错误进行的攻击),CLR支持跨语言调试,统一的错误报告(都用异常和原来返回的错误码说再见吧),全新的安全策略(CAS),兼容以往的COM组件。

  集成开发环境:Visual Studio 可以用来创建 Windows 平台下的 Windows 应用程序和网络应用程序,也可以用来创建网络服务、智能设备应用程序和 Office 插件

     什么是CLI?

     通用语言基础结构(Common Language Infrastructure,CLI)是CLR的一个子集,也就是.NET中最终对编译成MSIL代码的应用程序的运行环境进行管理的那一部分。在CLR结构图中CLI位于下半部分,主要包括类加载器(Class Loader)、实时编译器(IL To Native Compilers)和一个运行时环境的垃圾收集器(Garbage Collector)。CLI是.Net和CLR的灵魂,CLI为IL代码提供运行的环境,你可以将使用任何语言编写的代码通过其特定的编译器转换为MSIL代码之后运行其上,甚至还可以自己写MSIL代码在CLI上面运行。作为.Net与CLR的核心部分,CLI与C#也同时获得了ECMA的批准(ECMA-335)。拥有了C#与CLI这两项标准,你可以自己写出能够运行于任何操作系统上的.Net平台(只要你愿意)。如前所述,著名的Mono项目就是这么干的,Mono项目包括三个核心的部分:一个C#语言的编译器,一个CLI和一个类库。

      什么是CLR?

  CLR(Common Language Runtime)公共语言运行时是一个可由多种编程语言使用的“运行时”。CLR的核心功能(比如内存管理、程序集加载、安全性、异常处理小、线程同步)可由CLR的所有语言共用。

  IL是什么?

  IL中间语言,Dot Net的程序经过编译以后就形成了IL代码,在运行的时候CLR将IL编译成本地CPU指令。IL代码也称为托管代码。IL可以访问CLR的所有功能。IL也可视为一种面向对象的机器语言,可以使用汇编语言来编写IL。IL优势在于它会验证代码的正确性(参数数量,参数类型的验证)

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

  JIT(Just In Time)即时编译器,由CLR调用,将IL编译成本地CPU指令。

  当一段代码第一次被调用的时候,CLR指向包含在CLR内部的一个特殊函数,这个特殊函数就是JITCompiler,JITCompiler负责将IL编译成本地指令。JITCompiler知道实际调用的是哪个方法,以及该方法是那些类型定义的,JITCompiler会在定义该类型的程序集的元数据中查找被调用的方法的IL,并将IL编译成本地的CPU指令。编译的结果被放在一个内存块中,然后JITCompiler返回CLR为类型创建的内部数据结构,找到被实际调用方法对应的那条记录,修改最初对JITCompiler的引用,让其指向内存块中该被调用方法刚刚被编译好的CPU指令的地址。最后执行被调用方法的CPU指令。

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

  垃圾回收(garbage collection)

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

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

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

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

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

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

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

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

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

      先看MSDN上的C# 语言规范上的定义:

     类和结构是 .NET Framework 中的常规类型系统的两种基本构造。两者在本质上都属于数据结构,封装着一组整体作为一个逻辑单位的数据和行为。数据和行为是该类或结构的“成员”,它们包含各自的方法、属性和事件等.

    类或结构的声明类似于蓝图,用于在运行时创建实例或对象。如果定义一个名为 Person 的类或结构,则 Person 为类型名称。如果声明并初始化 Person 类型的变量 p,则 p 称为 Person 的对象或实例。可以创建同一 Person 类型的多个实例,每个实例在其属性和字段中具有不同的值。

      类是一种“引用类型”。创建类的对象时,对象赋值到的变量只保存对该内存的引用。将对象引用赋给新变量时,新变量引用的是原始对象。通过一个变量做出的更改将反映在另一个变量中,因为两者引用同一数据。

     结构是一种值类型。创建结构时,结构赋值到的变量保存该结构的实际数据。将结构赋给新变量时,将复制该结构。因此,新变量和原始变量包含同一数据的两个不同的副本。对一个副本的更改不影响另一个副本。

类通常用于对较为复杂的行为建模,或对要在创建类对象后进行修改的数据建模。结构最适合一些小型数据结构,这些数据结构包含的数据以创建结构后不修改的数据为主。

   结构与类共享大多数相同的语法,但结构比类受到的限制更多

  1. 在结构声明中,除非字段被声明为 const 或 static,否则无法初始化。
  2. 结构不能声明默认构造函数(没有参数的构造函数)或析构函数。

  3. 结构在赋值时进行复制。

    将结构赋值给新变量时,将复制所有数据,并且对新副本所做的任何修改不会更改原始副本的数据。在使用值类型的集合(如 Dictionary<string, myStruct>)时,请务必记住这一点。
  4. 结构是值类型,而类是引用类型

  5. 与类不同,结构的实例化可以不使用 new 运算符。

  6. 结构可以声明带参数的构造函数。

  7. 一个结构不能从另一个结构或类继承,而且不能作为一个类的基。

    所有结构都直接继承自 System.ValueType,后者继承自 System.Object。(其实就是值类型与引用类型的区别)
  8. 结构可以实现接口。

  9. 结构可用作可以为 null 的类型,因而可向其赋 null 值。

     .NET BCL里有哪些是类(结构),为什么它们不是结构(类)?
    BCL(Base Class Library)是Dot Net Framework下所有语言使用的类库(有哪些类和结构,你就随便说吧,System名字空间的有多少啊~需要注意的是DateTime这玩意其实是一个结构体。)为什么他们不是结构(类)呢?这道题和下面的小问题其实是一个问题那就是什么时候定义结构什么时候定义类,下面一起回答
    在自定义类型时,您如何选择是类还是结构?
          struct 类型适于表示 PointRectangleColor 等轻量对象。尽管使用自动实现的属性将一个点表示为类同样方便,但在某些情况下使用结构更加有效。例如,如果声明一个 1000 个 Point 对象组成的数组,为了引用每个对象,则需分配更多内存;这种情况下,使用结构可以节约资源。因为 .NET Framework 包含一个名为 Point 的对象,所以本示例中的结构命名为“CoOrds”。复数、坐标系中的点或字典中的“键-值”对都是结构的典型示例。
      除非满足一下所有条件,否则不要定义成值类型(结构体)
      第一,类型具有基元类型的行为。类型简单,其中没有成员会修改类型的任何实例字段。
      第二,类型不需要从其他任何类型继承。
      第三,类型不会派生出其他任何类型。
      除了满足以上全部条件,还必须满足以下条件中的一个。
      第一,类型的实例较小(约是16字节或者更小)。
      第二,类型实例较大,但不作为方法的实参传递,也不通过方法返回。
在.NET程序运行过程中,什么是堆,什么是栈?什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗?“结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗?
  在.NET程序运行过程中,什么是堆,什么是栈?

  堆也就是托管堆(managed heap),进程初始化的时候,CLR要保留一块连续的地址空间,这个地址空间最初并没有对应的物理存储空间。这个地址空间就是托管堆。

  栈是在程序运行过程中用于保存指令,值类型变量的内存区域(一个线程对应一个栈),栈的结构和数据结构中“栈”的结构是一样的,“先进后出”。

  什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗?

  值类型在栈上分配,引用类型在堆上分配。由于在栈上分配数据不受垃圾回收的控制,不存在垃圾回收的各种开销,应用程序执行的时候垃圾回收的次数也会少很多,所以栈上分配显然要比堆上分配性能上好。由于在栈上分配的变量已经包含了实例的字段所以不需要一个指针指向它。空间上的开销也较小。

  “结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗?

      这点不是太明确:值类型不作为对象在堆中分配。但在许多情况下,都需要获取对值类型的一个实例引用。例如假定要建个ArrayList来容纳Point结构。

 //声明一个值类型。         struct Point { public int x, y;}        public sealed class Program        {            public static void Main()            {                ArrayList a = new ArrayList();                 Point p;                 for (int i = 0; i < 10; i++)                 {                    p.x = p.y = i; a.Add(p);                 }            }        }

   这个时候 Point 值类型必须转换成一个真正 的,在堆中托管的对象,而且必须 获取对这个对象的引用。 这就是装箱的机制。 (不知道老赵说的是不是这种情况,还望老赵予以解答,还希望知道的朋友探讨下是不是这种情况)

      下面是装箱的过程:

       1.在托管堆中分配好内存。分配的内存是值 类型的各个字段需要的内存量加上托管的所有对象都 有的两个额外成员(类型对象指针和同步索引)需要的内存量。

       2 值类型的字段复制到新分配的内存

       3 返回对象的地址。现在,这个地址是对一个对象的引用,值类型现在是一个引用类型。

      在运行时,当前存在于Point 值类型的实例P中的字段会复制到新分配的Point对象中。已经装箱的Point 对象(现在是一个引用类型)的地址会返回给Add方法。Point对象会一直存在于堆中。

     如果是这种情况那么需要注意的地方就:这样会产生装箱/ 拆箱的操作。建议用泛型集合类。 FCl现在包含一组泛型集合类。这样不需要对集合中的项进行装箱、拆箱的处理,使性能提高不少,托管堆中需要创建的对象减少了,减少了应用程序中需要执行的垃圾回收次数。

泛型的作用是什么?它有什么优势?它对性能有影响吗?它在执行时的行为是什么?.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[]的区别是什么,平时你如何进行选择?Dictionary是做什么的?.NET BCL中还有哪些常用的容器?它们分别是如何实现的(哪种数据结构)?分别是适用于哪些场景?

   List和T[]的区别是什么,平时你如何进行选择?

  List是一个可以定义成无限长度的泛型列表,T[]是一个泛型的数组,数组你在定义的时候不得不给他定义长度。一般都用List显然它使用方便本身自带的方法也多,而且他是无限长度的,可以根据需要不断地追加。需要注意的是数组本身是继承自Object的所以他总是在堆上分配。

   Dictionary是做什么的?

  这个是字典的泛型类,对应于原来的HashTable。

   .NET BCL中还有哪些常用的容器?它们分别是如何实现的(哪种数据结构)?分别是适用于哪些场景?

  除了上面提到的两个最最常用的List和Dictionary外还有如下容器:

  Queue和Stack,两者都是不允许随机访问的列表,Queue是先进先出的队列,Stack是先进后出的栈。

  SortedList和SortedDictionary,两者都在枚举的时候返回一个按照Key排好序的列表,区别在于前者使用较少的内存,而后者在添加项的时候会快一些,但是如果本来就是排好序并依次添加那么前者更快。

  LinkedList,是一个链表,这个集合没有对应的非泛型版本,使用一个链表是为了在其中轻松的插入一个元素,随机访问性能要比数组慢,空间占用也更多一些,它和List不同之处在于它没有预分配容量来用作扩充,所以也就没有“浪费”的空间。如果你需要能在头尾都能够快速插入或者在中部插入,并且顺序的输出,那么链表还是有用的。

 

原创粉丝点击