基础类型和语法
Q: System.Object中包含哪些方法,哪些是虚方法
System.Object包含了Finalize在内的8个方法,其中有3个虚方法:Equals、GetHashCode和ToString方法。
Q: 值类型和引用类型的区别
所有的值类型都继承自System.ValueType,常用的值类型包括结构、枚举、整数型、浮点型、布尔型等。值类型的赋值会产生一个新的数据副本,所以每个值类型都拥有一个数据副本,而引用类型的赋值则是赋值引用。值类型的对象分配在堆栈上,而引用类型的对象分配在堆上。当比较两个值类型时,进行的是内容比较,比较两个引用类型时,进行的是引用比较。
Q: 简述装箱和拆箱原理
分析:装箱就是把堆栈上的值类型移到堆上;拆箱就是把堆中的对象复制到堆栈中,并且返回其值。先看一下装箱所需要的步骤:在堆上分配一个内存空间,大小等于需要装箱的值类型对象的大小加上两个引用类型对象都拥有的成员:类型对象指针和同步块引用;把堆栈上的值类型对象复制到堆上新分配的对象;返回一个指向堆上新对象的引用,并且存储到堆栈上被装箱的那个值类型的对象里。
答案:装箱和拆箱本质上是值类型在转换到System.Object时引发的堆栈和堆的一系列移动操作。装箱时值类型从堆栈上被复制到堆上,而拆箱时从堆上复制到堆栈上。装箱和拆箱对性能有较大的影响。
Q: C#中是否有全局变量
C#没有传统的意义的全局变量,在C#程序中,任何对象数据都必须属于某个类型。通过公共静态变量可以实现全局变量的功能。
Q: struct和class的区别,struct适用哪些场合
结构是值类型而类是引用类型。结构不具备继承的特性,结构不能拥有无参构造方法,也不能为成员变量定义初始值。结构常用于存储数据的集合,如果涉及复杂操作则应该设计成类而不是结构。
Q: 类型的初始化器何时被调用
类型的初始化器是指具有和类型相同名字、无参数无返回并且以static定义的方法。在类型的任何成员被使用之前调用类型初始化器。
Q: C#中方法的参数可以有哪几种传递方法
ref、out和params。ref和out都实现了参数的引用传递,区别在于ref要求参数在传入前被初始化,out要求参数在方法返回前被初始化。params实现了参数数目可变的方法。
Q: .NET支持哪几种可访问性级别,C#实现了其中哪几种
.NET中定义了6种可访问级别,分别为Private、Family、Assembly、Family&Assembly、Family or Assembly和Public。C#实现了除Family&Assembly之外的其他5种可访问级别,并且分别对应的关键字是private、protected、internal、protected internal和public。
Q: 简述属性的特点及属性和方法的异同
C#中属性是指有返回值而无参数的一种特殊的方法,允许程序员方便地为成员变量提供一对get/set(或者其他任意一个)方法,属性的使用和公共成员变量完全一致,却拥有更好的可扩展性。属性的本质和传统的GetXXX/SetXXX函数一样,但语法更优美,代码更加简洁易读。
Q: 简述C#中的浅复制和深复制
浅复制是指复制类型中的所有值类型成员,而只复制引用类型成员的引用,并且使目标对象共享原对象的引用类型成员对象。深复制是指同时复制值类型成员和引用类型成员的对象。
Q: C#中的using语句有什么作用
using语句为实现了IDisposable的类型对象调用Dispose方法,using语句能够保证使用的对象的Dispose方法在using语句块结束时被调用。C#编译器在编译时自动为using语句加上try/finally块。所有using使用的对象都应该在using语句开始后再初始化,以保证所有的对象能够被Dispose。
内存管理和垃圾回收
Q: 简述.NET中堆栈和堆的特点和差异
.NET程序在进程内存中分配出堆栈、托管堆和非托管堆。所有值类型对象和引用类型对象的引用都分配在堆栈上,堆栈根据对象的生存周期来依次分配和释放,堆栈根据一个指向栈尾的指针来分配内存,效率较高。
.NET所有引用类型对象分配在托管堆上,托管堆连续分配内存,受.NET的垃圾回收机制管理,效率相对堆栈来说要低。非托管堆不受.NET垃圾回收机制管理,内存块由程序员手动申请和释放。
Q: .NET中GC的运行机制
垃圾回收是指收集释放托管堆上不再被使用的对象内存。其基本过程包括:通过算法找到不再被使用的对象、移动对象使所有仍被使用的对象紧靠托管堆的一边和调整各个状态变量。垃圾回收的运行成本较高,对性能影响较大。
Q: Dispose方法和Finalize方法在何时被调用
Dispose方法被使用者主动调用,Finalize方法在对象被垃圾回收的第一轮回收后,由一个专用的.NET线程进行调用。Dispose方法不能保证被执行,而.NET的垃圾回收机制保证了拥有Finalize方法并且需要被调用的类型对象的Finalize方法被执行。调用Finalize方法涉及一系列复杂操作,性能代价高,程序员可以通过GC.SupressFinalize方法通知.NET该对象的Finalize方法不需要被调用。
下面是一个正确使用Dispose和Finalize的代码模板:
public class FinalizeDisposeBase : IDisposable { private bool _disposed = false; ~FinalizeDisposeBase() { Dispose(false); } //这里实现了IDispose中的Dispose方法 public void Dispose() { Dispose(true); //告诉GC此对象的Finalize方法不再需要调用 GC.SuppressFinalize(true); } //在这里做实际的析构工作 //申明为虚方法以供子类在有必要时重写 protected virtual void Dispose(bool isDisposing) { if (_disposed) { return; } if (isDisposing) { //在这里释放托管资源 //只在用户调用Dispose方法时执行 } //在这里释放非托管资源 //标记对象已被释放 _disposed = true; } }
public sealed class FinalizeDispose : FinalizeDisposeBase { private bool _mydisposed = false; protected override void Dispose(bool isDisposing) { if (_mydisposed) { return; } if (isDisposing) { //在这里释放托管的并且在这个类型中申明的资源 } //在这里释放非托管的并且在这个类型中申明的资源 //调用父类的Dispose方法来释放父类中的资源 base.Dispose(isDisposing); //设置子类的标记 _mydisposed = true; } }
Q: GC中的代是什么,一共分为几代
3代:0、1、2代。越小的代拥有越多的被释放的机会,而每一次GC中仍存活的对象实例将被移到下一代中。
Q: GC机制中如何判断一个对象是否仍在被使用
GC通过在使用的根引用遍历所有引用的对象实例,当一个对象不能被遍历时,将视为不再被使用。
Q: .NET的托管堆中是否可能出现内存泄露现象
内存泄露是指内存空间上产生了不再被实际使用却又不能被分配的内存。内存泄露将导致主机的内存随程序的运行而逐渐减少。
.NET的托管堆内可能出现严重内存泄露现象,主要产生原因有:大对象的频繁分配和释放、不恰当的保留根引用和错误的Finalize方法。
面向对象的实现
Q: 简述C#中重写、重载和隐藏的概念
重写是指用override关键字重新实现基类中的虚方法,在运行过程中,无论通过哪个类型的引用,真正对象类型的方法将被调用。隐藏是指用new关键字重新实现基类中的方法。重载是指多个方法共享同一个方法名但参数不同(注意,重载与方法的返回类型无关,即返回类型不能区别函数是否重载)。
Q: 为什么在构造方法中调用虚方法会导致问题
构造方法中调用虚方法将导致运行时错误。这是因为当基类的构造方法被调用时,子类的构造方法还未被调用,但是根据实际对象的类型,基类调用的虚方法是定义在子类中的方法,这就意味着调用了一个未完成构造的类型对象的方法,不可预计的错误将可能发生。
异常的处理
Q: 如何针对不同的异常进行捕捉
C#中一个try块可以有多个catch块,每个catch可以针对特别的异常进行特别的处理。为了安全起见,最后应该添加对Exception类型的异常进行捕获的catch块,以保证没有异常被毫无处理地抛出。
Q: 如何使用Conditional特性
Conditional特性用于编写在某个特定编译版本中运行的方法,通常它编写一些在Debug版本中支持测试的方法。当版本不匹配时,编译器会把Conditional特性的方法内容置空。
Q: 如何避免类型转换时的异常
用is和as语句代替直接使用强制转换,可以有效避免InvalidCastException异常,执行效率相对较高。