C#笔试题目总结
基础
知识点
try catch finally
的执行顺序(有return的情况下):
- 不管有没有出现异常,finally块中代码都会执行;
- 当try和catch中有return时,finally仍然会执行;
- finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前确定的;
- finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
题目
1.1 CTS、CLS、CLR分别作何解释?
CTS:Common Type System 通用系统类型,一种确定公共语言运行库如何定义,使用和管理类型的规范。
Int32、Int16->int、String->string、Boolean->bool,前者都是.Net中的类型,后者是C#中对这些类型的别名。
CLR:Common Language Runtime 公共语言运行时,.NET Framework 提供了一个称为公共语言运行库的运行时环境,它运行代码并提供使开发过程更轻松的服务。
CLS:Common Language Specification 通用语言规范,要和其他对象完全交互,而不管这些对象是以何种语言实现的,对象必须只向调用方公开那些它们必须与之互用的所有语言的通用功能。为此定义了公共语言规范 (CLS),它是许多应用程序所需的一套基本语言功能。
1.2 能用foreach遍历访问的对象的要求?
需要实现IEnumerable接口或声明GetEnumerator方法的类型。
1.3 下面程序的执行结果是什么?
static void Main(string[] args)
{
Console.WriteLine(Calc());
Console.ReadKey();
}
static int Calc()
{
int i = 0;
try
{
return i;
}
finally
{
Console.WriteLine("finally");
i++;
}
}
答案:
finally
0
解答:return先执行,finally后执行,所以return的值是没有i++之前的0。注意并不是return的时候函数真的就“返回、执行结束”了,return只是标记函数的返回值是0,标记完了还会执行finally中的代码,只有finally中的代码执行完成后函数才真正的返回。
1.4 如何把一个Array复制到ArrayList里?
实现1 string[] s ={ "111", "22222" }; ArrayList list = new ArrayList(); list.AddRange(s);
实现2 string[] s ={ "111", "22222" }; ArrayList list = new ArrayList(s);
数组是相同类型的集合。数组大小在它声明的时候就固定了。链表和数组相似,但它没有固定的大小。
1.5 什么是强类型,什么是弱类型?哪种更好些?为什么?
//C#中
int i = 3;
i = "a"; //不可以
//JavaScript中
var i = 3;
i = "a"; //可以
强类型是在编译的时候就确定类型的数据,在执行时类型不能更改,而弱类型在执行的时候才会确定类型。
没有好不好,二者各有好处,强类型安全,因为它事先已经确定好了,而且效率高。弱类型更灵活,但是效率低,而且出错概率高
一般用于编译型编程语言,如c++,java,c#,pascal等,弱类型相比而言不安全,在运行的时候容易出现错误,但它灵活,多用于解释型编程语言,如javascript,vb等
1.6 概述反射和序列化
反射:程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性,它是在运行时创建和使用类型实例。
序列化:序列化是将对象转换为容易传输的格式的过程。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象。在另一端,反序列化将从该流重新构造对象。
1.7 运算符++?
++ a 表示先将a加1,然后表达式的值为a加1后的值,a ++表示(表达式的值为a,然后a加1)。
1.8 什么是类型参数化?
类型参数化是一个类型在另一个值或者类型之上参数化。
1.9 你对泛型了解吗?简单说明一下泛型的好处?
泛型:通过参数化类型来实现在同一份代码上操作多种数据类型。利用“参数化类型”将类型抽象化,从而实现灵活的复用。
好处是:类型安全和减少装箱、拆箱;提高性能、类型安全和质量,减少重复性的编程任务。
1.10 new有几种用法?
第一种:实例化对象new Class();
第二种:覆盖基类方法public new XXXX() {}
第三种:new 约束指定泛型类声明中的任何类型参数都必须有公共的无参数构造函数
1.11 DateTime.Parse(myString);
这行代码有什么问题?
有问题,当myString不能满足时间格式要求的时候,会引发异常,建议使用DateTime.TryParse()。
1.12 为什么不提倡catch(Exception) ,catch(Exception e){ throw e; }和catch(Exception e){throw;},error和exception区别?
第一个将发生的异常对象抛出,另一个只是抛出异常,并没有抛出原异常对象,try...catch在出现异常的时候影响性能; 应该捕获更具体得异常,比如IOExeception,OutOfMemoryException等,更多详细请看[异常][2]
error表示恢复不是不可能但很困难的情况下的一种严重问题。比如说内存溢出。不可能指望程序能处理这样的情况。
exception 表示一种设计或实现问题。也就是说,它表示如果程序运行正常,从不会发生的情况。
1.13 out 和 ref的区别与相同点?
out 和 ref都指示编译器传递参数地址,在行为上是相同的;
他们的使用机制稍有不同,ref要求参数在使用之前要显式初始化,out要在方法内部初始化;out 和 ref不可以重载,就是不能定义Method(ref int a)和Method(out int a)这样的重载,从编译角度看,二者的实质是相同的,只是使用时有区别:
- 使用ref型参数时,传入的参数必须先被初始化,对out而言,必须在方法中对其完成初始化
- 使用ref和out时,在方法的参数和执行方法时,都要加ref或out关键字,以满足匹配
- out适合用在需要retrun多个返回值的地方,而ref则用在需要被调用的方法修改调用者的引用的时候
1.14 请解释一下IClonable?
IClonable方法是实现深度复制(Deep Copy)的接口,实现它应该能深度复制一个对象出来。深度复制的特征是调用对象的构造方法,创建新的对象,包括创建对象中嵌套的引用对象的新实例。而浅复制(Shadow Copy)则不同,是浅表复制,不重新创建新实例。浅表复制的实现是Object.MemberWiseClone()。
1.15 using关键字有什么用?什么是IDisposable?
using可以声明namespace的引入
还可以实现非托管资源的释放,实现了IDisposiable的类在using中创建,using结束后会自动调用该对象的Dispose方法,释放资源。using其实等价于try……finally,用起来更方便。
1.16 什么叫应用程序域?什么是受管制的代码?什么是托管代码?
应用程序域为安全性.可靠性.版本控制以及卸载程序集提供了隔离边界。应用程序域通常由运行库宿主创建,运行库宿主负责在运行应用程序之前引导公共语言运行库。应用程序域提供了一个更安全.用途更广的处理单元,公共语言运行库可使用该单元提供应用程序之间的隔离。
受管制的代码:在.Net环境中运行的任何代码都是受管制的代码(managed code),.Net外部的代码也运行在windows上,这些代码称为未受管制的代码(unmanaged code)。
使用基于公共语言运行库的语言编译器开发的代码称为托管代码;托管代码具有许多优点,例如:跨语言集成.跨语言异常处理.增强的安全性.版本控制和部署支持.简化的组件交互模型.调试和分析服务等。
1.17 请叙述const与readonly的区别?
const 关键字用于修改字段或局部变量的声明。它指定字段或局部变量的值不能被修改。常数声明引入给定类型的一个或多个常数。
const数据成员的声明式必须包含初值,且初值必须是一个常量表达式。因为它是在编译时就需要完全评估。
const成员可以使用另一个const成员来初始化,前提是两者之间没有循环依赖。
readonly在运行期评估赋值,使我们得以在确保“只读访问”的前提下,把object的初始化动作推迟到运行期进行。
readonly 关键字与 const 关键字不同: const 字段只能在该字段的声明中初始化。readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。另外,const 字段是编译时常数,而 readonly 字段可用于运行时常数。
readonly 只能在声明时或者构造函数里面初始化,并且不能在 static 修饰的构造函数里面。
1.18 public static const int A = 1;
这段代码有错误么?
错误:const不能被修饰为static ;因为定义为常量 (const )后就是静态的(static ),并不能显式使用。
1.19 C#的有哪些注释类型?
C#中有三种注释类型:单行(//)、多行(/* */)、Page/XML 注释(///)。
1.20 C#有哪些特点?
简单 类型安全 灵活 面向对象 兼容 持久化 互操作性 有别于传统
C#是一个简单而强大的编程语言,用于编写企业版的应用程序。它是C++和VB的混合体。它保留了许多C++特性,如语句,表达式和运算符并结合了VB的生产力。
C#帮助开发者轻易地构建网络服务,能够通过任何语言,任何平台来访问Internet。
C#帮助开发者用更少的代码完成开发,从而在代码中错误更少。
C#引入了相当大的改进和创新,如类型安全,版本控制,事件和垃圾收集这些领域。
1.21 public、static和void之间的区别是什么?
public:关键字public是访问修饰符,用来告诉C#编译器主(Main)方法可以被任何人调用。
static:关键字static表明主(Main)方法是一个全局方法,不需要穿件类实例即可访问。编译器储存该方法的地址作为切入点,并利用这个信息在任何对象创建之前开始执行它。
void:关键字void是一个类型修饰符表明主(Main)方法不返回任何值。
1.22 C#中有哪些类型的数组?
一维数组、多维数组、交错数组
数组
1.23 什么是交错数组?
交错数组是元素为数组的数组。
交错数组元素的维度和大小可以不同。
交错数组有时称为“数组的数组”。
数组
1.24 C#中支持哪些语句类型?
C#支持的几种不同的语句类型是
块语句 声明语句 表达式语句 选择语句 迭代语句 跳转语句 异常处理语句 检查和未检查 Lock语句
1.25 什么是方法?
方法是由对象或者类执行来实现计算或者操作的成员,静态方法通过类访问,实例方法通过类的实例来访问。
1.26 错误的类型是什么?
语法错误(Syntax error) 逻辑错误(Logic error) 运行时错误(Runtime error)
1.27 break和continue语句之间有什么区别?
break语句是用来终止当前封闭循环或者它所在的条件语句的。
continue语句是用来改变执行顺序的。和break语句那样跳出循环相反,continue语句停止当前迭代并且只将控制返回到循环顶部。
1.28 什么是参数?
参数是用来传递值或者变量引用给方法的。方法的参数从被调用方法时指定的参数得到它们实际的值。有四种参数:值参数,引用参数,输出参数和参数数组。
方法
1.29 C#中运算符的含义是什么?
运算符是界定了对类实例应用特定的运算表达式内涵的成员。可以定义三种类型的运算符:一元运算符,二元运算符和转换运算符。所有的运算符必须声明为public和static的。
1.30 Array和LinkedList之间的区别是什么?
数组是不关心彼此元素位置的简单数字序列。他们之间的位置彼此独立。增加,删除或者修改任何数组元素都是非常容易的。相对于数组,链表是一个复杂的数字序列。
1.31 C#支持可变数目的参数吗?
是的,使用params关键字。该参数指定为特定类型的参数列表。
方法
1.32 return语句怎么用?
return语句与程序 (方法或者函数)相关。在执行return语句的时候,系统将控制权从被调用程序交给调用程序。return语句用于两种目的:
- 立即返回当前执行的代码的调用者
- 返回给当前执行的代码的调用者一些值。
1.33 goto语句怎么用?
goto语句仍然包含在C#语言中。这个goto可以用来从一个循环内部跳转到外部。但是从循环外部跳入循环内是不允许的。
1.34 在switch语句中break语句是做什么的?
break语句终结它所在的循环。它也改变了程序执行的流程。
在switch语句中,break语句用在一个case语句的结尾处。在C#中break语句是强制性的,避免了一个case语句流向另一个case语句。
1.35 C#中有哪些不同的文本类型?
布尔值: True和False是Boolean类型,分别映射到真和假的状态。
整数:用于编写类型Int,uInt,long和ulong的值。
实数:用于编写类型float, double和decimal的值。
字符:代表单字符,通常由有引号的字符组成,如‘a’。
字符串: C#支持两种类型的字符串,规则字符串和原义字符串。规则字符串由0个或多个括在双引号中的字符组成,如“116110″。原义字符串由@字符后跟带双引号的字符组成,如@”hello”。
Null: 代表null类型。
1.36 什么是标识符?
标识符无他,它是用来在程序中唯一识别各种实体的名称。
标识符
1.37 子程序和函数的主要区别是什么?
子程序没有返回值,而函数有。
1.38 什么是泛型?
泛型帮助我们创建灵活的强类型集合。
泛型基本上从数据类型中分离了逻辑,以保持更好的可重用性,更好的可维护性等等。
1.39 C#中while循环和do while循环的基本区别是什么?
while循环在一开始测试它的条件,这意味着如果条件求值为真,封闭的语句块执行0或者更多次。do while循环至少遍历一次语句块然后在最后才检查条件。
1.40 什么是.Net FrameWork ?
定义:.Net FrameWork类似于JVM(虚拟机),他是微软Web Services的引擎。我们习惯的叫做.NET框架,同时我们写的C#,VB.NET,等程序必须在.Net框架上运行。
组成:.Net FrameWork由两部分组成,分别是公共语言运行时(CLR),统一类库集(FCL)。
公共语言运行时(CLR):包括两部分分别为,公共语言规范(CLS)及公共类型规范(CTS)。
统一类库集(FCL):涵盖了我们以后开发中要用到的基本框架类,ADO.NET,WINFORM,WEBFORM,WEBSERVICES等类库集。
值类型与引用类型
2.1 值类型和引用类型的区别?
值类型包括简单类型、结构体类型和枚举类型,引用类型包括自定义类、数组、接口、委托等。
- 赋值方式:将一个值类型变量赋给另一个值类型变量时,将复制包含的值。这与引用类型变量的赋值不同,引用类型变量的赋值只复制对象的引用(即内存地址,类似C++中的指针),而不复制对象本身。
- 继承:值类型不可能派生出新的类型,所有的值类型均隐式派生自System.ValueType。但与引用类型相同的是,结构也可以实现接口。
- null:与引用类型不同,值类型不可能包含 null 值。然而,可空类型功能允许将 null 赋给值类型。
- 每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值,值类型初始会默认为0,引用类型默认为null。
- 值类型存储在栈中,引用类型存储在托管堆中。
2.2 结构和类的区别?
结构体是值类型,类是引用类型,主要区别如2.1。
其他的区别:结构不支持无惨构造函数,不支持析构函数,并且不能有protected修饰;结构常用于数据存储,类class多用于行为;class需要用new关键字实例化对象,struct可以不适用new关键字; class可以为抽象类,struct不支持抽象;
class可以被实例化,属于引用类型,是分配在内存的堆上的,类是引用传递的。
struct属于值类型,是分配在内存的栈上的,结构体是复制传递的。
2.3 delegate是引用类型还是值类型?enum、int[]和string呢?
enum枚举是值类型,其他都是引用类型。
2.4 堆和栈的区别?
线程堆栈:简称栈 stack,托管堆:简称堆 heap。
值类型大多分配在栈上,引用类型都分配在堆上;栈由操作系统管理,栈上的变量在其作用域完成后就被释放,效率较高,但空间有限。堆受CLR的GC控制;栈是基于线程的,每个线程都有自己的线程栈,初始大小为1M。堆是基于进程的,一个进程分配一个堆,堆的大小由GC根据运行情况动态控制;
栈(stack)由系统管理生存期,存储代码执行和调用路径,执行或调用完毕即从栈中清除。
堆(heap)中保存值和对象,调用完毕之后依然存在,由垃圾回收器查找栈中有无指向该值或对象的引用,无则从堆中删除。
2.5 “结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗?
结构是值类型,有两种情况会分配在堆上面: 结构作为class的一个字段或属性,会随class一起分配在堆上面; 装箱后会在堆中存储,尽量避免值类型的装箱,值类型的拆箱和装箱都有性能损失
2.6 理解参数按值传递?以及按引用传递?
按值传递:对于值类型传递的它的值拷贝副本,而引用类型传递的是引用变量的内存地址,他们还是指向的同一个对象。
按引用传递:通过关键字out和ref传递参数的内存地址,值类型和引用类型的效果是相同的。
2.7 有几种方法可以判定值类型和引用类型?
继承自System.ValueType的是值类型,反之是引用类型。
2.8 C#支持哪几个预定义的值类型?C#支持哪些预定义的引用类型?
值类型:整数、浮点数、字符、bool和decimal 引用类型:Object,String
2.9 说说值类型和引用类型的生命周期?
值类型在作用域结束后释放。
引用类型由GC垃圾回收期回收。
2.10 如果结构体中定义引用类型,对象在内存中是如何存储的?例如下面结构体中的class类 User对象是存储在栈上,还是堆上?
public struct MyStruct
{
public int Index;
public User User;
}
MyStruct存储在栈中,其字段User的实例存储在堆中,MyStruct.User字段存储指向User对象的内存地址。
装箱与拆箱
3.1 什么是拆箱和装箱?
装箱就是值类型转换为引用类型,拆箱就是引用类型(被装箱的对象)转换为值类型。
3.2 什么是箱子?
就是引用类型对象。
3.3 箱子放在哪里?
托管堆上。
3.4 装箱和拆箱有什么性能影响?
装箱和拆箱都涉及到内存的分配和对象的创建,有较大的性能影响。
3.5 如何避免隐身装箱?
编码中,多使用泛型、显示装箱。
3.6 箱子的基本结构?
箱子就是一个引用类型对象,因此她的结构,主要包含两部分:值类型字段值;引用类型的标准配置,引用对象的额外空间:TypeHandle和同步索引块
3.7 装箱的过程?
下面的图来自装箱
- 在堆中申请内存,内存大小为值类型的大小,再加上额外固定空间(引用类型的标配:TypeHandle和同步索引块);
- 将值类型的字段值(x=1023)拷贝新分配的内存中;
- 返回新引用对象的地址(给引用变量object o)
var i = 123; //System.Int32
//对 i 装箱(隐式)进对象 o
object o = i;
对象 o 存的是地址引用,指向的是堆上的值,这个值的类型和变量 i 一样,也是 int 类型,值(123)也就是从变量 i 拷贝过来的一个副本值而已。
【备注】装箱默认是隐式的,当然,你可以选择显式,但这并不是必须的。
3.8 拆箱的过程?
- 检查实例对象(object o)是否有效,如是否为null,其装箱的类型与拆箱的类型(int)是否一致,如检测不合法,抛出异常;
- 指针返回,就是获取装箱对象(object o)中值类型字段值的地址;
- 字段拷贝,把装箱对象(object o)中值类型字段值拷贝到栈上,意思就是创建一个新的值类型变量来存储拆箱后的值;
int i = 123; // 值类型
object o = i; // 装箱
int j = (int)o; // 拆箱
要在运行时成功拆箱值类型,被拆箱的项必须是对一个对象的引用,该对象是先前通过装箱该值类型的实例创建的。
拆箱时需要注意,转换出现异常的情形:
虽然,decimal 类型可以直接强转为 int 类型,但从调式的结果来看,拆箱时是会引发“转换无效”的异常。要记住,拆箱时强转的值类型,应以装箱时的值类型一致。
string与字符串操作
知识点
常量池
为了提高性能和保证执行效率,.Net Framework维护了一个字符串池(暂存池、常量池),用以对使用过的字符串进行缓存。当为字符串类型的变量赋值时,此值同时被放入常量池中,在以后需要创新新的字符串时,CLR会首先检查池中是否存在相同值的字符串对象,如果存在,就将变量指向池中已经存在的对象,那么旧的对象又得以重用了,这个过程称为“字符串驻留”。而常量池缓存字符串对象的原则是:直接出现在代码中的字符串都会被放入“常量池”,而动态创建的字符串对象并不会被放入。可使用string.IsInterned(string)进行测试。
string的不可变性:
下面的图来自string的不可变性
字符串对象是不可变的:即它们创建之后就无法更改。 所有看似修改字符串的 String 方法和 C# 中的运算符,实际上都以新字符串对象的形式返回结果。 在下面的示例中,当连接 s1 和 s2 的内容以形成一个字符串时,不会修改两个原始字符串。 += 运算符会创建一个包含组合内容的新字符串。 这个新对象赋给变量 s1,而最初赋给 s1 的对象由于没有其他任何变量包含对它的引用而释放,将在后续被垃圾回收。
示例一:
static void Main(string[] args)
{
var s1 = "Hi!";
var s2 = "Fanguzai!";
//拼接 s1 和 s2,并且修改 s1 指向的值
s1 += s2; //即 s1 = s1 + s2;
Console.WriteLine(s1);
Console.Read();
}
图:var s1 = "Hi!"; var s2 = "Fanguzai!";
图:s1 = s1 + s2; 重新修改 s1 的指向
由于“修改”字符串实际上是创建一个新字符串,因此创建对字符串的引用时必须谨慎。 如果创建了对字符串的引用,然后“修改”原始字符串,则该引用指向的仍是原始对象,而不是修改字符串时创建的新对象。
static void Main(string[] args)
{
var s1 = "Hi! ";
var s2 = s1;
//在 s1 重新赋值后,这次没有重新修改 s2 指向的值
s1 += "Fanguzai!"; //即 s1 = s1 + "Fanguzai!";
Console.WriteLine(s2);
Console.Read();
}
图:var s1 = "Hi!"; s2 = s1; 他们指向相同的引用地址
图:s1 = s1 + "Fanguzai!"; 会创建一个没有引用的 "Fanguzai!",并重新修改 s1 指向的值。
4.1 字符串是引用类型类型还是值类型?
引用类型
4.2 在字符串连加处理中,最好采用什么方式,理由是什么?
少量字符串连接,使用String.Concat。
大量字符串使用StringBuilder,因为StringBuilder的性能更好,由于string的不可变性,如果使用string的话会创建大量字符串对象。
4.3 String s = new String("xyz");
创建了几个String Object?
两个对象,一个是“xyz”,一个是指向“xyz”的引用对象。
4.4 是否可以继承String类?
String类是sealed类故不可以继承。
4.5 string与String的区别
string、int是C#定义的类型,而String、Int32是.net类型即是CTS类型;
string 是 .NET 框架中 System.String 的别名。
string在编译的时候会转化为String类
4.6 String str=new String("a")和String str = "a"有什么区别?
String str = "a"; 这个只是一个引用,内存中如果有“a"的话,str就指向它,如果没有才创建如果你以后还用到"a"这个字符串的话并且是这样用: String str1 = "a"; String str2 = "a"; String str2 = "a";
这4个变量都共享一个字符串"a" 而String str = new String("a");
是根据"a"这个String对象再次构造一个String对象,将新构造出来的String对象的引用赋给str
string字符串
4.7 判断字符串变量str是否为空的方法?
- str=="";
- str==String.Empty
- str.Length==0;String.IsNullOrEmpty(str)
微软的实现
// Token: 0x060002C0 RID: 704 RVA: 0x00380D97 File Offset: 0x0037EF97
[NonVersionable]
public static bool IsNullOrEmpty(string value)
{
return value == null || 0 >= value.Length;
}
4.8 string str = null 与 string str = “” 说明其中的区别?
string str = null 是不给他分配内存空间,而string str = "" 给它分配长度为空字符串的内存空间。 string str = null没有string对象,string str = “”有一个字符串对象。
4.9 StringBuilder 和 String 的区别?
String 在进行运算时(如赋值.拼接等)会产生一个新的实例,而 StringBuilder 则不会。所以在大量字符串拼接或频繁对某一字符串进行操作时最好使用 StringBuilder,不要使用 String
如果要操作一个不断增长的字符串,尽量不用String类,改用StringBuilder类。两个类的工作原理不同:String类是一种传统的修改字符串的方式,它确实可以完成把一个字符串添加到另一个字符串上的工作没错,但是在.NET框架下,这个操作实在是划不来。因为系统先是把两个字符串写入内存,接着删除原来的String对象,然后创建一个String对象,并读取内存中的数据赋给该对象。这一来二去的,耗了不少时间。而使用System.Text命名空间下面的StringBuilder类就不是这样了,它提供的Append方法,能够在已有对象的原地进行字符串的修改,简单而且直接。当然,一般情况下觉察不到这二者效率的差异,但如果你要对某个字符串进行大量的添加操作,那么StringBuilder类所耗费的时间和String类简直不是一个数量级的。
4.10 不是说字符串是不可变的吗?string s="abc";s="123"不就是变了吗?
String是不可变的在这段代码中,s原先指向一个String对象,内容是 "abc",然后我们将s指向"123",那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另一个 String对象,内容为"123",原来那个对象还存在于内存之中,只是s这个引用变量不再指向它了。
类与接口
知识点
构造函数的执行顺序
创建类的实例时,会调用类的构造函数,为了实例化派生的类,必须实例化它的基类;要实例化这个基类,有必须实例化这个基类的基类,这样一直实例化System.Object(所有类的根),因此无论使用什么构造函数实例化一个类,总是首先调用System.Object.Object().
5.1 所有类型都继承System.Object吗?
基本上是的,所有值类型和引用类型都继承自System.Object,接口是一个特殊的类型,不继承自System.Object。
5.2 解释virtual、sealed、override和abstract的区别
- virtual申明虚方法的关键字,说明该方法可以被重写
- sealed说明该类不可被继承
- override重写基类的方法
- abstract申明抽象类和抽象方法的关键字,抽象方法不提供实现,由子类实现,抽象类不可实例化
5.3 接口和类有什么异同?
不同点:
- 接口不能直接实例化
- 接口只包含方法或属性的声明,不包含方法的实现。
- 接口可以多继承,类只能单继承。
- 类有分部类的概念,定义可在不同的源文件之间进行拆分,而接口没有。(这个地方确实不对,接口也可以分部,谢谢@xclin163的指正)
- 表达的含义不同,接口主要定义一种规范,统一调用方法,也就是规范类,约束类,类是方法功能的实现和集合
相同点:
- 接口、类和结构都可以从多个接口继承。
- 接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。
- 接口和类都可以包含事件、索引器、方法和属性。
5.4 抽象类和接口有什么区别?
抽象类一种不可以被实例化的类。抽象类中一般含有抽象方法,当然也可有具体实现。继承类只有实现过所有抽象类的抽象方法后才能被实例化。
相同点:都不能被直接实例化,都可以通过继承实现其抽象方法。
不同点:
- 继承:接口支持多继承;抽象类不能实现多继承。
- 表达的概念:接口用于规范,更强调契约,抽象类用于共性,强调父子。抽象类是一类事物的高度聚合,那么对于继承抽象类的子类来说,对于抽象类来说,属于"Is A"的关系;而接口是定义行为规范,强调“Can Do”的关系,因此对于实现接口的子类来说,相对于接口来说,是"行为需要按照接口来完成"。
- 方法实现:对抽象类中的方法,即可以给出实现部分,也可以不给出;而接口的方法(抽象规则)都不能给出实现部分,接口中方法不能加修饰符。
- 子类重写:继承类对于两者所涉及方法的实现是不同的。继承类对于抽象类所定义的抽象方法,可以不用重写,也就是说,可以延用抽象类的方法;而对于接口类所定义的方法或者属性来说,在继承类中必须重写,给出相应的方法和属性实现。
- 新增方法的影响:在抽象类中,新增一个方法的话,继承类中可以不用作任何处理;而对于接口来说,则需要修改继承类,提供新定义的方法。
- 接口可以作用于值类型(枚举可以实现接口)和引用类型;抽象类只能作用于引用类型。
- 接口不能包含字段和已实现的方法,接口只包含方法、属性、索引器、事件的签名;抽象类可以定义字段、属性、包含有实现的方法。
5.5 重载与覆盖的区别?
重载:当类包含两个名称相同但签名不同(方法名相同,参数列表不相同)的方法时发生方法重载。用方法重载来提供在语义上完成相同而功能不同的方法。
覆写:在类的继承中使用,通过覆写子类方法可以改变父类虚方法的实现。
主要区别:
- 方法的覆盖是子类和父类之间的关系,是垂直关系;方法的重载是同一个类中方法之间的关系,是水平关系。
- 覆盖只能由一个方法,或只能由一对方法产生关系;方法的重载是多个方法之间的关系。
- 覆盖要求参数列表相同;重载要求参数列表不同。
- 覆盖关系中,调用那个方法体,是根据对象的类型来决定;重载关系,是根据调用时的实参表与形参表来选择方法体的。
5.6 在继承中new和override相同点和区别?看下面的代码,有一个基类A,B1和B2都继承自A,并且使用不同的方式改变了父类方法Print()的行为。测试代码输出什么?为什么?
static void Main(string[] args)
{
B1 b1 = newB1();
B2 b2 = new B2();
b1.Print();
b2.Print(); //输出 B1、B2
A ab1 = newB1(); A ab2 = new B2();
ab1.Print(); ab2.Print(); //输出 B1、A
Console.ReadLine();
}
public class A
{
public virtual void Print() { Console.WriteLine("A"); }
}
public class B1 : A
{
public override void Print() { Console.WriteLine("B1"); }
}
public class B2 : A
{
public new void Print() { Console.WriteLine("B2"); }
}
![](file:///C:\Users\Eric\AppData\Local\Temp\ksohtml8796\wps9.png)
5.7 class中定义的静态字段是存储在内存中的哪个地方?为什么会说她不会被GC回收?
随类型对象存储在内存的加载堆上,因为加载堆不受GC管理,其生命周期随AppDomain,不会被GC回收。
5.8 简述 private、 protected、 public、 internal、protected internal 访问修饰符和访问权限
private : 私有成员, 在类的内部才可以访问。
protected : 保护成员,该类内部和继承类中可以访问。
public : 公共成员,完全公开,没有访问限制。
internal: 当前程序集内可以访问。
protected internal: 访问仅限于当前程序集或从包含类派生的类型。
不带修饰符的类是默认internal。
5.9 简述abstract、sealed类修饰符
abstract:可以被指示一个类只能作为其它类的基类。
sealed:指示一个类不能被继承。
5.10 简述C#成员修饰符
abstract:指示该方法或属性没有实现。
const:指定域或局部变量的值不能被改动。
event:声明一个事件。
extern:指示方法在外部实现。
override:对由基类继承成员的新实现。
readonly:指示一个域只能在声明时以及相同类的内部被赋值。
static:指示一个成员属于类型本身,而不是属于特定的对象。
virtual:指示一个方法或存取器的实现可以在继承类中被覆盖。
5.11 C#中索引器是否只能根据数字进行索引?是否允许多个索引器参数?
参数的个数和类型都是任意的。用reflector反编译可以看出,索引器的内部本质上就是set_item、get_item方法。
5.12 C#支持多重继承么?
类之间不支持,接口之间支持。类对接口叫做实现,不叫继承。
5.13 是否可以从一个static方法内部发出对非static方法的调用?
不可以。因为非static方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而static方法调用时不需要创建对象,可以直接调用。也就是说,当一个static方法被调用时,可能还没有创建任何实例对象,如果从一个static方法中发出对非static方法的调用,那个非static方法是关联到哪个对象上的呢?这个逻辑无法成立,所以,一个static方法内部不能发出对非static方法的调用。
5.14 什么时候使用抽象类,什么时候用接口
接口用于规范,抽象类用于共性。
接口中只能声明方法,属性,事件,索引器。而抽象类中可以有方法的实现,也可以定义非静态的类变量。抽象类是类,所以只能被单继承,但是接口却可以一次实现多个。抽象类可以提供某些方法的部分实现,接口不可以。抽象类的实例是它的子类给出的。接口的实例是实现接口的类给出的。再抽象类中加入一个方法,那么它的子类就同时有了这个方法。而在接口中加入新的方法,那么实现它的类就要重新编写(这就是为什么说接口是一个类的规范了)。接口成员被定义为公共的,但抽象类的成员也可以是私有的、受保护的、内部的或受保护的内部成员(其中受保护的内部成员只能在应用程序的代码或派生类中访问)。此外接口不能包含字段、构造函数、析构函数、静态成员或常量。
5.15 在对象比较中,对象一致和对象相等分别是指什么?a.Equals(b)和a == b一样吗?
对象一致是指两个对象是同一个对象,引用相同。而对象相等是指两个对象的值相同,但引用不一定相同。
不一样。a.Equals(b)表示a与b一致, a==b表示a与b的值相等。
上述描述有所偏差,比较片面,详见https://www.cnblogs.com/zagelover/articles/2741409.html,感谢评论区逗比猴子的指正
5.16 虚方法(virtual )和抽象方法(abstract)的区别?
- 抽象方法仅有声明,而没有任何实现,如abstract someMethod();,虚方法却不能如此
- 子类继承父类,可以对父类中的虚方法进行重写、覆盖、不处理三种处理,对抽象方法却必须实现
5.17 子类对父类中虚方法的处理有重写(override)和覆盖(new),请说明它们的区别?
有父类ParentClass和子类ChildClass、以及父类的虚方法VirtualMethod。有如下程序段:
ParentClass pc = new ChildClass();
pc.VirtualMethod(...);
如果子类是重写(override)父类的VirtualMethod,则上面的第二行语句将调用子类的该方法
如果子类是覆盖(new)父类的VirtualMethod,则上面的第二行语句将调用父类的该方法
https://www.cnblogs.com/home-wang/p/10948854.html
5.18 那么如果这些接口中有重复的方法名称呢?
这种情况中你可以决定如何实现。当然需要特别得小心。但是在编译环节是没有问题的。
https://www.cnblogs.com/home-wang/p/10949248.html
5.19 为什么不能指定接口中方法的修饰符?
接口中的方法用来定义对象之间通信的契约,指定接口中的方法为私有或保护没有意义。他们默认为公有方法。
5.20 何时必须声明一个类为抽象类?
当这个类中包含抽象方法时,或是该类并没有完全实现父类的抽象方法时。
5.21 能够阻止某一个类被其他类继承么?
可以,使用关键字sealed。
5.22 能够实现允许某个类被继承,但不允许其中的某个方法被覆写么?
可以,标记这个类为public,并标记这个方法为sealed。
5.23 可以覆写私有的虚方法么?
不可以,甚至子类中无法访问父类中的私有方法。
5.24 能够将非静态的方法覆写成静态方法么?
不能,覆写方法的签名必须与被覆写方法的签名保持一致,除了将virtual改为override。
5.25 私有成员会被继承么?
会,但是不能被访问。所以看上去他们似乎是不能被继承的,但实际上确实被继承了。
5.26 传入某个属性的set方法的隐含参数的名称是什么?
value,它的类型和属性所声明的类型相同。
5.27 如何在C#中实现继承?
在类名后加上一个冒号,再加上基类的名称。
5.28 C#提供一个默认的无参数构造函数,当我实现了另外一个有一个参数的构造函数时候,还想保留这个无参数的构造函数。这样我应该写几个构造函数?
两个,一旦你实现了一个构造函数,C#就不会再提供默认的构造函数了,所以需要手动实现那个无参数构造函数。
5.29 面向对象的语言具有什么样的特性?
封装.继承.多态。
5.30 在.Net中所有可序列化的类都被标记为_____?
[serializable]
5.31 请叙述属性与索引器的区别
属性 | 索引器 |
---|---|
通过名称标识 | 通过签名标识 |
通过简单名称或成员访问来访问 | 通过元素访问来访问 |
可以为静态成员或实例成员 | 必须为实例成员 |
属性的 get 访问器没有参数 | 索引器的 get 访问器具有与索引器相同的形参表 |
属性的 set 访问器包含隐式 value 参数 | 除了 value 参数外,索引器的 set 访问器还具有与索引器相同的形参表 |
5.32 您在什么情况下会用到虚方法?它与接口有什么不同?
子类重新定义父类的某一个方法时,必须把父类的方法定义为virtual
在定义接口中不能有方法体,虚方法可以。
实现时,子类可以不重新定义虚方法,但如果一个类继承接口,那必须实现这个接口。
5.33 继承有哪些不同的类别?
单继承:包括一个基类和一个派生类。
多层继承(Hierarchical inheritance) :包括一个基类和继承自同一个基类的派生类。
多级继承(Multilevel inheritance):包括从一个派生类派生出来的类。
多重继承(Multiple inheritance):包括多个基类和一个派生类。
5.34 面向对象编程的基本概念是什么?
有必要理解一些在面向对象编程中广泛使用的概念。它们包括:对象、类、数据抽象和封装、继承、 多态性、动态绑定、信息传递。
5.35 Define scope?定义作用域?
作用域指的是代码中一个变量可以被访问的区域。
5.36 对象是什么?
对象是类的实例。对象的创建使用new操作。一个类在内存中创建一个对象,将包含特定对象的值和行为(或者方法)的信息。
5.37 对象和实例之间的区别是什么?
用户定义的类型的实例称为一个对象。我们可以从一个类实例化很多对象。对象是类的实例。
5.38 定义析构函数?
当类对象超出作用域或者被明确删除的时候,析构函数被调用。析构函数,顾名思义是用来销毁由构造函数创建的对象的。正如构造函数,析构函数是一个类成员方法,方法名和类名相同,只是由波浪号开头。
https://www.cnblogs.com/home-wang/p/10944310.html
5.39 定义多态性?
多态性意味着一个名字,多种形式。它使我们在一个程序中可以定义一个以上相同名字的方法。它让我们重载和覆写操作以便这样的操作可以对不同的实例表现不同的行为。
https://www.cnblogs.com/home-wang/p/10949050.html
5.40 “this”可以在静态方法中用吗?
不,‘this’不能在静态方法中使用。仅仅只有静态变量/方法可以在静态方法中使用。
5.41 什么是基类?
类声明可以通过类名加一个冒号和基类名来指定基类。省略基类说明等同于从object类派生。
5.42 静态方法和实例方法的区别是什么?
以static修饰符声明的方法是静态方法。静态方法不操作具体的实例,并且只能被静态成员访问。
没有以static修饰符声明的方法是实例方法。实例方法操作一个具体的实例并且可以被静态和实例成员访问。在其上调用实例方法的实例可以像这样显示访问。在静态方法中这么调用是错误的。
5.43 可以重写私有虚方法吗?
不可以,私有方法不能在类外访问。
5.44 什么是数据封装?
数据封装,也被称为数据隐藏,它是类的实现细节对保持对用户隐匿的机制。用户只能通过执行称为方法的特殊函数来对隐藏了成员的类执行一组有限的操作。
5.45 什么是嵌套类?
嵌套类是类中的类。
嵌套类是声明发生在另一个类或者接口里面的任何类。
5.46 可以给静态构造函数参数吗?
不可以,静态构造函数不可以有参数。
https://www.cnblogs.com/home-wang/p/10944310.html
5.47 C#提供拷贝构造函数吗?
不,C#不提供拷贝构造函数。
5.48 类或者结构可以有多个构造函数吗?
可以,类或者结构可以有多个构造函数。C#中构造函数可以被重载。
5.49 可以创建接口的实例吗?
不可以,你不可以创建接口的实例。
5.50 接口可以包含字段吗?
不可以,接口不能包含字段。
5.51 为什么在代码中用virtual关键字?
代码中Virtual关键字是用来定义可以在派生类中重写的方法和属性的。
5.52 编译时多态性和运行时多态性的区别是什么?
编译时多态性:也被称为方法重载,是指有两个或更多同名但含有不同签名的方法。
运行时多态性:也被称为方法重写,是指有两个或更多的同名方法,含有相同的方法签名但对应不同的实现。
https://www.cnblogs.com/home-wang/p/10949050.html
5.53 在C#中可以声明一个静态块吗?
不可以,因为C#不支持静态块,但它支持静态方法。
5.54 C#中什么是密封类?
sealed修饰符用来阻止从一个类派生。如果一个密封类被指定为另一个类的基类时会发生编译时错误。
5.55 什么是静态成员?
定义为静态的成员,可以从类级别上直接调用,而不是从类实例上调用。
5.56 抽象方法和虚方法之间的区别是什么?
抽象方法不提供实现,并且强制派生类重写该方法(除非派生类也是个抽象类),而虚方法可以有实现并且在派生类中重写与否是可选的。因此虚方法可以实现并提供了派生类重写的选择。抽象方法不能提供实现并且强制派生类重写该方法。
5.56 什么是静态方法?
只要方法不试图访问任何实例数据或者其他实例方法,那么声明它为静态的是可能的。
常量、字段、属性、特性、委托和事件
6.1 const和readonly有什么区别?
const关键字用来声明编译时常量,readonly用来声明运行时常量。都可以标识一个常量,主要有以下区别:
- 初始化位置不同。const必须在声明的同时赋值;readonly即可以在声明处赋值,也可以在构造方法里赋值。
- 修饰对象不同。const即可以修饰类的字段,也可以修饰局部变量;readonly只能修饰类的字段
- const是编译时常量,在编译时确定该值,且值在编译时被内联到代码中;readonly是运行时常量,在运行时确定该值。
- const默认是静态的;而readonly如果设置成静态需要显示声明。
- 支持的类型时不同,const只能修饰基元类型或值为null的其他引用类型;readonly可以是任何类型。
![](file:///C:\Users\Eric\AppData\Local\Temp\ksohtml8796\wps10.png)
public class Program
{
public readonly int PORT;
static void Main(string[] args)
{
B a = new B();
Console.WriteLine(a.PORT);
Console.WriteLine(B.PORT2);
Console.WriteLine(a.PORT3.ccc);
Console.ReadLine();
}
}
class B
{
//readonly即可以在声明处赋值,也可以在构造方法里赋值
public readonly int PORT;
//const必须在声明的同时赋值
public const intPORT2 = 10;
//错误public const A PORT3 = new A() const只能修饰基元类型或值为null的其他引用类型
//readonly可以是任何类型
public readonlyA PORT3 = new A();
public B()
{
PORT = 11;
}
}
class A
{
public stringccc = "aaaa";
}
}
![](file:///C:\Users\Eric\AppData\Local\Temp\ksohtml8796\wps11.png)
6.2 字段与属性有什么异同?
- 属性提供了更为强大的,灵活的功能来操作字段
- 出于面向对象的封装性,字段一般不设计为public
- 属性允许在set和get中编写代码
- 属性允许控制set和get的可访问性,从而提供只读或者可读写的功能 (逻辑上只写是没有意义的)
- 属性可以使用override 和 new
6.3 静态成员和非静态成员的区别?
- 静态变量使用 static 修饰符进行声明,静态成员在类被使用的时候就被加载,通过类进行访问。
- 不带有static 修饰符声明的变量称做非静态变量,在对象被实例化时创建,通过对象进行访问 。
- 一个类的所有实例的同一静态变量都是同一个值,同一个类的不同实例的同一非静态变量可以是不同的值 。
- 静态函数的实现里不能使用非静态成员,如非静态变量、非静态函数等。
6.4 特性是什么?如何使用?
特性与属性是完全不相同的两个概念,只是在名称上比较相近。Attribute特性就是关联了一个目标对象的一段配置信息,本质上是一个类,其为目标元素提供关联附加信息,这段附加信息存储在dll内的元数据,它本身没什么意义。运行期以反射的方式来获取附加信息。
6.5 C#中委托的主要作用是什么?
委托主要用于定义回调方法。
6.6 C#中的委托是什么?事件是不是一种委托?
什么是委托?简单来说,委托类似于 C或 C++中的函数指针,允许将方法作为参数进行传递。C#中的委托都继承自System.Delegate类型;委托类型的声明与方法签名类似,有返回值和参数;委托是一种可以封装命名(或匿名)方法的引用类型,把方法当做指针传递,但委托是面向对象、类型安全的;
事件可以理解为一种特殊的委托,事件内部是基于委托来实现的。
委托和事件没有可比性,因为委托是类型,事件是对象,下面说的是委托的对象(用委托方式实现的事件)和(标准的event方式实现)事件的区别。事件的内部是用委托实现的。因为对于事件来讲,外部只能“注册自己+=、注销自己-=”,外界不可以注销其他的注册者,外界不可以主动触发事件,因此如果用Delegate就没法进行上面的控制,因此诞生了事件这种语法。事件是用来阉割委托实例的,类比用一个自定义类阉割List。事件只能add、remove自己,不能赋值。事件只能+=、-=,不能= 。加分的补充回答:事件内部就是一个private的委托和add、remove两个方法。
6.7 特性能够放到某个方法的参数上?如果可以,这有什么用?
可以,作用可以对参数有进一步限定,比如输入参数为int类型,可以通过允许AttributeTargets=ParameterInfo的Attribute自定义实现来限定输入参数的大小,比如当输入参数小于100的时候便报错。
6.8 声明一个委托public delegate int myCallBack(int x);
则用该委托产生的回调方法的原型应该是(b )。
a) void myCallBack(int x) ;
b) int receive(int num) ;
c) string receive(int x) ;
d) 不确定的;
6.9 什么是多播委托?
每个委托对象保持对一个单独方法的引用。但是,一个委托对象保持对多个方法的引用并调用它们是可能的。这样的委托对象成为多播委托或者组合委托。
委托
6.10 事件有返回值吗?
没有,事件没有返回类型。
6.11 事件是什么?
事件是一个基于另一个程序方法执行的动作。
事件是被对象或者类使用来通知其他对象发生的事件的委托类型类成员。
事件可以通过event关键字来声明。
事件是使一个类或对象能够提供通知的成员。事件声明像域声明一样,除了声明包含event关键字并且类型必须为委托类型。
事件
6.12 事件可以用访问修饰符吗?
可以,你可以在事件中用访问修饰符。你可以对事件使用portected关键字,这样只允许继承类访问。你可以使用private修饰事件,仅可以供当前类对象访问。
6.13 C#中get和set属性的优势是什么?
get属性访问器用于返回属性值。
set属性访问器用来设置新的值。
同时还可以设置不同的访问级别来进行访问限制。
6.14 属性和public字段的区别是什么?调用set方法为一个属性设值,然后用get方法读取出来的值一定是set进去的值吗?
属性可以对设值、取值的过程进行非法值控制,比如年龄禁止设值负数,而字段则不能进行这样的设置。虽然一般情况下get读取的值就是set设置的值,但是可以让get读取的值不是set设置的值的,极端的例子。public Age{ get { return 100; } set { } }
。用reflector反编译可以看出,属性内部本质上就是set_XXX、get_XXX方法。
GC与内存管理
7.1 简述一下一个引用对象的生命周期?
- new创建对象并分配内存
- 对象初始化
- 对象操作、使用
- 资源清理(非托管资源)
- GC垃圾回收
7.2 GC进行垃圾回收时的主要流程是?
- 标记:先假设所有对象都是垃圾,根据应用程序根Root遍历堆上的每一个引用对象,生成可达对象图,对于还在使用的对象(可达对象)进行标记(其实就是在对象同步索引块中开启一个标示位)。
- 清除:针对所有不可达对象进行清除操作,针对普通对象直接回收内存,而对于实现了终结器的对象(实现了析构函数的对象)需要单独回收处理。清除之后,内存就会变得不连续了,就是步骤3的工作了。
- 压缩:把剩下的对象转移到一个连续的内存,因为这些对象地址变了,还需要把那些Root跟指针的地址修改为移动后的新地址。
7.3 GC在哪些情况下回进行回收工作?
- 内存不足溢出时(0代对象充满时)
- Windwos报告内存不足时,CLR会强制执行垃圾回收
- CLR卸载AppDomian,GC回收所有
- 调用GC.Collect
- 其他情况,如主机拒绝分配内存,物理内存不足,超出短期存活代的存段门限
7.4 using() 语法是如何确保对象资源被释放的?如果内部出现异常依然会释放资源吗?
using() 只是一种语法形式,其本质还是try…finally的结构,可以保证Dispose始终会被执行。
7.5 解释一下C#里的析构函数?为什么有些编程建议里不推荐使用析构函数呢?
C#里的析构函数其实就是终结器Finalize,因为长得像C++里的析构函数而已。
有些编程建议里不推荐使用析构函数要原因在于:第一是Finalize本身性能并不好;其次很多人搞不清楚Finalize的原理,可能会滥用,导致内存泄露,因此就干脆别用了
7.6 Finalize() 和 Dispose() 之间的区别?
Finalize() 和 Dispose()都是.NET中提供释放非托管资源的方式,他们的主要区别在于执行者和执行时间不同:
- finalize由垃圾回收器调用;dispose由对象调用。
- finalize无需担心因为没有调用finalize而使非托管资源得不到释放,而dispose必须手动调用。
- finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;而dispose一调用便释放非托管资源。
- 只有class类型才能重写finalize,而结构不能;类和结构都能实现IDispose。
另外一个重点区别就是终结器会导致对象复活一次,也就说会被GC回收两次才最终完成回收工作,这也是有些人不建议开发人员使用终结器的主要原因。
7.7 Dispose和Finalize方法在何时被调用?
- Dispose一调用便释放非托管资源;
- Finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;
7.8 .NET中的托管堆中是否可能出现内存泄露的现象?
是的,可能会。比如:
- 不正确的使用静态字段,导致大量数据无法被GC释放;
- 没有正确执行Dispose(),非托管资源没有得到释放;
- 不正确的使用终结器Finalize(),导致无法正常释放资源;
- 其他不正确的引用,导致大量托管对象无法被GC释放;
7.9 在托管堆上创建新对象有哪几种常见方式?
- new一个对象;
- 字符串赋值,如string s1=”abc”;
- 值类型装箱;
7.10 GC是什么? 为什么要有GC?
GC是垃圾收集器。程序员不用担心内存管理,因为垃圾收集器会自动进行管理。GC只能处理托管内存资源的释放,对于非托管资源则不能使用GC进行回收,必须由程序员手工回收,一个例子就是FileStream或者SqlConnection需要程序员调用Dispose进行资源的回收。
7.11 .Net中会存在内存泄漏吗,请简单描述。
所谓内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。.Net中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象变成了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。虽然.Net可以回收无用的对象,但是.Net仍然存在由于使用不当导致的内存泄露问题。.Net中的内存泄露的情况:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是.Net中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是.Net中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局Dictionary对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。扩展:使用弱引用那么即使被引用也可以被回收。
7.12 你对.net的GC的理解
GC是.Net的垃圾收集器,可以进行内存资源的回收,程序员无需关心资源的回收,当一个对象没有任何引用的时候就可以被回收了。一个对象可以被回收并不意味着一定会被立即回收,GC会选择时机进行回收。可以调用GC.Collect()让GC立即回收。GC不能回收非托管资源,对于非托管资源一般都实现了IDisposable接口,然后使用using关键字进行资源的回收。
7.13 我们怎么抑制finalize方法?
GC.SuppressFinalize()。
多线程编程与线程同步
8.1 描述线程与进程的区别?
- 一个应用程序实例是一个进程,一个进程内包含一个或多个线程,线程是进程的一部分;
- 进程之间是相互独立的,他们有各自的私有内存空间和资源,进程内的线程可以共享其所属进程的所有资源;
8.2 lock为什么要锁定一个参数,可不可锁定一个值类型?这个参数有什么要求?
lock的锁对象要求为一个引用类型。她可以锁定值类型,但值类型会被装箱,每次装箱后的对象都不一样,会导致锁定无效。
对于lock锁,锁定的这个对象参数才是关键,这个参数的同步索引块指针会指向一个真正的锁(同步块),这个锁(同步块)会被复用。
8.3 多线程和异步有什么关系和区别?
多线程是实现异步的主要方式之一,异步并不等同于多线程。实现异步的方式还有很多,比如利用硬件的特性、使用进程或线程等。在.NET中就有很多的异步编程支持,比如很多地方都有BeginXXX、EndXXX的方法,就是一种异步编程支持,她内部有些是利用多线程,有些是利用硬件的特性来实现的异步编程。
8.4 线程池的优点有哪些?又有哪些不足?
优点:减小线程创建和销毁的开销,可以复用线程;也从而减少了线程上下文切换的性能损失;在GC回收时,较少的线程更有利于GC的回收效率。
缺点:线程池无法对一个线程有更多的精确的控制,如了解其运行状态等;不能设置线程的优先级;加入到线程池的任务(方法)不能有返回值;对于需要长期运行的任务就不适合线程池。
8.5 Mutex和lock有何不同?一般用哪一个作为锁使用更好?
Mutex是一个基于内核模式的互斥锁,支持锁的递归调用,而Lock是一个混合锁,一般建议使用Lock更好,因为lock的性能更好。
参考引用
[1] 积少成多 C#面试题
[2] 异常
[3] 面试题
[4] C#面试题(一)
[5] C#面试题(二)
[6] C#面试题(三)