第一章 基础语法
1、.net单元测试框架
-
MSTest:MSTest是Microsoft提供的单元测试框架,内建在Visual Studio中,所以使用起来非常方便。对于.NET Framework和.NET Core都提供了良好的支持,而且传值测试也很容易上手。即使你想切换到其他框架,也只需要NuGet下个包,换下特性而已,相对来说比较容易。
-
NUnit:NUnit是.NET单元测试框架的标准之一,一度引领了.NET单元测试的潮流。它最初是Java单元测试框架JUnit直接移植过来的,之后在设计和可用性上做了改进。这意味着您可能会在各种教程和示例中看到大量NUnit的使用。它的传值测试功能也很强大。然而,这并不意味着它就是最好的选择。
2、理解函数式编程
函数式编程与其他编程方法的区别就在于函数式编程不会修改数据或状态。函数式编程适用于深度学习、机器学习、人工智能等需要在同一个数据集上执行不同操作的场景。
LINQ语法就是一个函数式编程的例子。之前使用过LINQ,其实就已经接触了函数式编程。
更改对象状态是程序缺陷的源泉,尤其是在多线程程序中。本章介绍了函数式编程如何避免更改对象状态来保证安全。
3、数组和链表区别
4、公共语言运行库(CLR)
公共语言运行库(CLR)的优点可归纳为自动内存管理及垃圾回收、类型安全、可扩展的元数据、结构化的错误处理和多线程。
5、什么是强类型,什么是弱类型?哪种更好些?
在JS语言中,一个变量在应用程序的生命周期中可以保存不同的类型,称为动态类型。
在C#中要实现这种做法的话,可以使用dynamic关键字;
强类型是在编译的时候就确定类型的数据,在执行时类型不能更改,而弱类型在执行的时候才会确定类型。
没有好不好,二者各有好处,强类型安全,因为它事先已经确定好了,而且效率高。弱类型更灵活,但是效率低,而且出错概率高
一般用于编译型编程语言,如c++,java,c#,pascal等,弱类型相比而言不安全,在运行的时候容易出现错误,但它灵活,多用于解释型编程语言,如javascript,vb等
6、.NET中非托管代码是什么
非托管代码是指直接编译成目标计算机的机器码。非托管代码不由公共语言运行库运行(CLR),而是由操作系统直接执行的代码。非托管代码必须自己提供垃圾回收、类型检查、安全支持等服务。
非托管资源:数据库链接、文件句柄、网络链接、com对象==;由Finalize和Dispose方法(实现IDisposable接口)清理释放资源;
7、GC垃圾回收
GC如其名,就是垃圾收集,针对托管代码,当然这里仅就内存而言。
对托管资源,.NET的对象生成通过new运算符操作,托管堆将分配内存空间给该对象,其生命周期由垃圾收集器(garbagecollection)管理。
GC只能处理托管内存资源的释放,对于非托管资源则不能使用GC进行回收,必须由程序员手工回收,一个例子就是FileStream或者SqlConnection需要程序员调用Dispose进行资源的回收。
垃圾收集器每次进行垃圾回收时,将对堆上的对象进行检查, 垃圾收集器遍历所有对象,通过一定的算法标记查找哪些是可回收的对象,并把没有任何变量引用的对象销毁。
垃圾收集器并不经常同时检查堆上的每个对象,只是将对象分类,严格地说将所有对象分为3代(generation),分别为0代、1代和2代。通常来说,生命时间越短的对象(新创建的对象)代数越小,反之代数越大。
在托管堆的空间不够用时,垃圾收集器将开始垃圾回收,检查第0代的对象,如果发现了没有被引用的对象,则标记这些对象为“垃圾”,并销毁这些对象。说明:垃圾收集器回收时,当前程序进程的所有活动线程将挂起。
GC是周期性执行内存清理工作,以下情况出现时GC将会启动:
1、内存不足溢出时,更确切地说是第0代对象充满时;
2、调用GC.Collect方法强制执行垃圾回收;
3、window报告内存不足,CLR将强制执行垃圾回收;
4、CLR卸载appDomain时,GC将所有代龄的对象执行垃圾回收;
不建议调用GC.Collect方法,除非知道有大量对象停止引用;
GC在垃圾回收之后,会出现托管堆的内存碎片,会重新分配内存,压缩托管堆;
8、遗留对象
当对象遗留在内存中时,它们可能导致内存泄漏。
静态变量可以通过几种形式造成内存泄漏。使用DependencyObject、INotifyPro-pertyChanged或者直接订阅事件都有可能造成内存泄漏。
使用静态变量引用的对象时,若后续不进行释放,也会造成内存泄漏。由于静态变量引用的对象属于垃圾回收器(Garbage Collection,GC)的根对象,而根对象会被垃圾回收器标记为不可回收,因此任何被静态变量引用的对象都会被垃圾回收器标记为不可回收。
使用匿名方法捕获类的成员时,相应类的实例也会被引用。只要匿名方法仍然存活,则该类的实例也会继续存活。
使用非托管代码(或COM)时,如不能释放相应的托管和非托管对象并显式释放内存,就会造成内存泄漏。
在无特定存储期限的缓存中不使用弱引用、不清理未使用的缓存或未限制缓存的大小都将令内存最终耗尽。
在不会终止的线程中创建对象引用也会导致内存泄漏。
非匿名引用类的事件订阅也可能造成内存泄漏。只要这些事件仍然被订阅,则相应的对象就依然会保留在内存中。除非在不再需要进行时间订阅时解绑,否则就非常可能造成内存泄漏。
9:如何设置类成员访问权限
private:私有成员,在类的内部才可以访问。protected:保护成员,该类内部和继承类中可以访问。public:公共成员,完全公开,没有访问限制。internal:在同一命名空间内可以访问。
10、Class 和Struct区别:
1、从引用类型和值类型角度区分,内存分配存储地方和垃圾回收方式不一样;
2、class可声明无参构造函数和析构函数;struct则不行;
3、class有继承,有多态、;struct只是支持接口继承,值类型是密封类型,不能被继承;
引用类型:分配在托管堆;受GC管理;
值类型:分配 在堆栈上,或者作为引用类型的一部分;由操作系统管理,当值类型实例所在方法结束时,其存储单位自动释放,栈的执行率高,但存储容量有限;
11、 值类型和引用类型的区别?
1.将一个值类型变量赋给另一个值类型变量时,将复制包含的值。引用类型变量的赋值只复制对对象的引用,而不复制对象本身。
2.值类型不可能派生出新的类型:所有的值类型均隐式派生自 System.ValueType。但与引用类型相同的是,结构也可以实现接口。
3.值类型不可能包含 null 值:然而,可空类型功能允许将 null 赋给值类型。
4.每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。
12、 堆和栈的区别?
栈是编译期间就分配好的内存空间,因此你的代码中必须就栈的大小有明确的定义;局部值类型变量、值类型参数等都在栈内存中。
堆是程序运行期间动态分配的内存空间,你可以根据程序的运行情况确定要分配的堆内存的大小。
13、内存分配的几点:
值类型变量做为局部变量时,该实例将被创建在堆栈上;
而如果值类型变量作为引用类型的成员变量时,它将作为引用类型实例数据的一部分,保存在托管堆上。
对于值类型嵌套引用类型的情况,引用类型变量作为值类型的成员变量,在堆栈上保存该成员的引用,而实际的引用类型保存在GC堆上;
托管堆的内存分配机制
托管堆又根据存储信息的不同划分为多个区域,其中最重要的是垃圾回收堆(GC Heap)和加载堆(Loader Heap),GC Heap用于存储对象实例,受GC管理;Loader Heap又分为High-Frequency Heap、Low-Frequency Heap和Stub Heap,不同的堆上又存储不同的信息。Loader Heap最重要的信息就是元数据相关的信息,也就是Type对象,每个Type在Loader Heap上体现为一个Method Table(方法表),而Method Table中则记录了存储的元数据信息,例如基类型、静态字段、实现的接口、所有的方法等等。Loader Heap不受GC控制,其生命周期为从创建到AppDomain卸载。
静态字段的内存分配和释放,又有何不同?
静态字段也保存在方法表中,位于方法表的槽数组后,其生命周期为从创建到AppDomain卸载。因此一个类型无论创建多少个对象,其静态字段在内存中也只有一份。静态字段只能由静态构造函数进行初始化,静态构造函数确保在类型任何对象创建前,或者在任何静态字段或方法被引用前执行。
13、.NET的自动内存管理
总结起来,.NET的自动内存管理,主要包括以下几个方面:
-
对象创建时的内存分配和初始化;
-
垃圾回收;
-
非托管资源释放;
CLR管理内存的区域,主要有三块:线程的堆栈(用于分配值类型,堆栈主要由操作系统管理,而不受GC控制,当值类型实例所在方法结束时,其存储单位自动释放,栈的执行率高,但存储容量有限);
GC堆(用于分配引用类型对象的实例大小小于85000字节)、LOH(large object heap)堆(用于分配引用类型对象的实例大小大于85000字节);
14、析构函数 和 Dispose
编译器将~ctor析构函数编译为一个Finalize方法;
因此,对于在类中重写了Finalize方法(在C#中实现析构函数),当GC启动时,对于判定为可回收垃圾对象,GC会自动执行其Finalize方法来清理非托管资源。
Finalize方法存在终止化操作时间无法控制,执行顺序无法保证,损伤性能。不过 垃圾 收集 器 需要 两次 回收 工作 才能 真正 清除 改 对象, 并且 由于 回收 时间 不确定 等 因素, 导致 程序 性能 受 影响。
因此,在使用终结器的情况下,如果创建对象的速度比垃圾回收的速度快,则会发生内存用尽异常。
故实现Dispose模式来完成对非托管资源进行清理。
如果类实现IDisposable接口的Dispose方法,可使该类对象成为可处置对象,在程序运行中可通过编程显式调用Dispose方法,达到及时清理非托管资源的目的。显式调用Dispose方法可以在非托管资源用完后马上将其清理,而且速度很快,相比析构函数更加有效。
不过编程者经常忘记调用Dispose方法,这将导致非托管资源长期生存于托管堆中。
使用using结构可以避免该问题,编写方法如以下代码所示:
//程序中使用using结构 using(类型名称对象名称=new构造函数) { 对象使用代码 }
Finalize方法﹕只能由微软调用
Dispose和Close方法﹕提供给您调用
实现Finalize 方法或析构函数对性能可能会有负面影响,因此应避免不必要地使用它们。若要在 C# 中实现 Finalize 方法,您必须使用析构函数语法。
15、Using在.Net中应用:
1、引入命名空间(是在逻辑上的组织架构,而非实际的物理结构,避免类名冲突的方法;代码中存在调用操作,则编译器才会加载using引入命名空间所在程序集);
2、创建别名(可创建类型别名和程序集别名)using alias=namespace|type;
3、强制非托管资源清理,编译器自动将using生成try-finally,在finally调用dispose方法;
支持初始化多个变量;
Dispose只是清理对象封装的非托管资源,而不是释放对象的内存(对象的内存由GC控制);
16、.NET的应用程序域是什么
应用程序域是 CLR 中提供代码运行范围、错误隔离和安全隔离的逻辑单元,功能类似于操作系统的进程。
17、请谈一谈C#中的引用类型
引用类型的变量又称为对象,可存储对实际数据的引用。
class、interface和delegate都可以用于声明引用类型。C#有两个内置的引用类型:object类型和string类型。
18、字符串处理
字符串是初始化后不能再修改的引用类型,由于字符串的特点和大量的使用,.NET提供了一种叫字符串池的机制对字符串的处理进行优化。所谓字符串池机制就是对内存中内容相同的字符串重复使用。
string 类型 变量 可以 使用 相等 以及 不等 运算符 比较 所 引用 的 对象。
而 一般 的 引用 类型 则 比较 引用 地址 是否 相等, 即是 否 指向 相同 的 对象。
string 类型 的 变量 所 引用 的 字符串 对象 创建 后 无法 修改, 当 程序 对 变量 进行 修改 操作 时, 变量 引用 的 字符串 对象 将 返回 一个 修改 的 副本。 相应 地, 直接 赋予 新的 字符串 值 给 该 变量 时, 相当于 创建 一个 新的 字符串 对象。
两次创建相同内容的string对象可以指向相同的内存地址;
string类是跨应用程序域,可以在不同应用程序域访问同一string对象;字符串驻留是进程级,被进程中所有appdomain所共享,其原因源于字符串的恒定性;其生命周期不受GC控制,垃圾回收不能释放哈希表中引用的字符串对象,只有进程结束这些对象才会被释放;
公共语言运行库通过维护一个表来存放字符串,该表称为拘留池,不过内存分配还是位于托管堆中;
CLR维护一个哈希表来管理其创建的大部分string对象;
string stra="abcdef";
string strC="abc";
string strD=strC+"def"; 其实是调用string.contact()
这种strD属于动态生成的字符串,没有添加到CLR内部维护的哈希表而使字符串驻留机制失效;要比较stra和strD是否相等,则可以使用Intern和IsInterned方法;
19、 String s = new String("xyz");创建了几个String Object?
两个对象,一个是“xyx”,一个是指向“xyx”的引用对象。
String s1=”d” string s2=”d” 是一个string对象,因为字符串拘留池,(因为字符串不可变,可以共享)
20、 是否可以继承String类?
String类是sealed类故不可以继承。
21、如何使用非整数表示数组的索引
C#中不能使用字符表示数组的索引,如果使用字符表示数组的索引,将出现错误。但是,可以通过在类中使用索引器来实现非整数表示数组的索引。
22、Hashtable有以下4种遍历方式:
A:以string为键 遍历哈希表;
B:以自定义对象为值 遍历哈希表;
C:以DictionaryEntry对象为键值遍历哈希表;
D:通过继承IDictionaryEnumerator接口的对象来遍历哈希表;
23、如何在byte[]和String之间进行转换
字符串和字节数组的转换依赖于编码方式,不同的编码标准将使用不同的算法进行。System.Text.Encoding提供(Encoding.UTF8.GetBytes(string)/.GetString(Byte[] b))进行转换。
10.Array(数组)与ArrayList(集合)的区别
相同点:
都是引用类型;
24、Array(数组)与ArrayList(集合)转换
遍历数组,然后add()加入集合中;
ToArray(),记得的强转换;
25、is和as
Is检查对象与某个类型之间是否兼容;
As用于在兼容的引用类型之间执行转换;
26、New关键词在.NET中应用:
1、作为运算符,用于创建对象和调用构造函数,分配内存并初始化;不可重载,若分配内存失败,将引发OutOfMemoryException异常;
2、作为修饰符,用于向基类成员隐藏继承成员;
3、作为约束,用于在泛型声明中约束可能用作类型参数的参数类型。
class Genericer where T:new() { }
Base 常用于在派生类对象初始化时和基类进行通信,访问基类共有成员和protected成员;
静态成员只有由类来访问,不能由对象访问,故不能使用this和base;
27、两个自定义class类型转换有几种方式
A:两个类型是继承关系;
B: operator 关键字来重载内置运算符,它可以定义不同类型之间采用何种转化方式和转化的结果。有隐式转换(implicit)和显示转换(explicit):
public static implicit operator 目标类型(被转化类型 变量参数) { return 目标类型结果; }
28、Nullable本质上继承于struct的值类型;
NUll object模式小结:有效解决对象为空的情况,提供统一判定的Isnull属性,可以通过实现Inullable接口,也可以通过Extension Method实现Isnull判断;
29、预处理指令
预处理指令主要用于辅助条件编译。不会在编译时转化成可执行代码,但会影响编译过程;
在 编译 C# 程序 时, 编程 者 往往 希望 能 控制 编译 的 过程, 如 编译 一部分 而 忽略 另一 部分, 类似 这种 工作 可以 由 C# 预处理 命令 完成。
(1)# region、# endregion, 可用 于 标记 自定义 的 代码 块 部分。
30、yield关键字:
yield这个语法糖实现了一个实现 IEnumerable接口的类来返回我们需要到 IEnumerable类型的数据。
当我们需要返回IEnumerable类型的时候,直接yield返回数据就可以了。也不用new一个list,或其他类型。所以yield是一个典型的语法糖。
作用于迭代器块中,用于像枚举器对象提供值或者发出结束信号;
yield return 用于依次返回每个元素;
yield break用于终止迭代;
当希望获取一个IEnumerable类型的集合,而不想把数据一次性加载到内存,就可以考虑使用yield return实现"按需供给"。
使用yield return为什么能保证每次循环遍历的时候从前一次停止的地方开始执行呢?
--因为,编译器会生成一个状态机来维护迭代器的状态。
static IEnumerable FilterWithYield() { foreach (int i in GetInitialData()) { if (i > 2) { yield return i; } } yield break; Console.WriteLine("这里的代码不执行"); }
在我们使用数据的时候被执行。如果对此机制不了解,就容易出现另外一些意想不到的问题。例如在Power方法中添加一些验证程序,如果不符合条件就抛出一个异常。这样的异常检查不会被执行。只有我们使用数据的时候才会执行。这样就失去了检查数据的意义。
具体的例子可以看Artech博主的文章
另外使用yield还有一些注意事项:
你不能在具有以下特点的方法中包含 yield return 或 yield break 语句:
匿名方法。
包含不安全的块的方法。
31、Const和readonly
Const(生成IL代码,static修饰,只能应用在值类型和string类型,常量值确定于编译时,常量编译后保存于模块的元数据中,无须在托管堆中分配内存;声明是必须初始化,可以定义字段和局部变量;)
readonly(只读字段,可以是任意类型,但是对于引用类型字段,readonly不能限制对该对象实例成员的读写控制;可在构造函数初始化,也就是在在运行时获取值,构造函数初始化是在运行时,只局限于定义字段)
推荐使用static readonly
32、特性和属性:
特性attribute,是一个类,为目标元素提供关联附加信息,并在运行期以反射的方式来获取附加信息;除了自定义的特性,常用特性有:[Flags]、[DllImport]、[Serializable],还有数据层应用的[Required]、[Description]等等;
属性:对私有字段的访问封装;
33、覆写(重写)和重载:
覆写就是子类中重复定义父类方法,只有virtual和abstract标记的方法(包括泛型)才能被覆写,覆写以override标记;
重载是在同一个类中存在多个方法同名而参数列表不同(包括泛型);方法地址是在编译期已经确定;一般包括方法重载和运算符重载;operator implict可实现一定程度的按返回值重载;
覆写是实现运行时的多态性,而重载实现了编译时的多态性;
34、深浅克隆
只有实现了ICloneable接口的类型,才允许其实例被克隆,在Clone方法中通过调用基类MenberwiseClone就可实现浅拷贝;深拷贝实现方法可以在clone里返回New 一个实例对象;
浅拷贝:是指将对象中的所有字段逐字复杂到一个新对象。
对值类型字段只是简单的拷贝一个副本到目标对象,改变目标对象中值类型字段的值不会反映到原始对象中,因为拷贝的是副本;
对引用型字段则是指拷贝他的一个引用到目标对象。改变目标对象中引用类型字段的值它将反映到原始对象中,因为拷贝的是指向堆是上的一个地址;
深拷贝:深拷贝与浅拷贝不同的是对于引用字段的处理,深拷贝将会在新对象中创建一个新的对象和原始对象中对应字段相同(内容相同)的字段,也就是说这个引用和原始对象的引用是不同, 我们改变新对象中这个字段的时候是不会影响到原始对象中对应字段的内容。
一般值类型之间的赋值是执行深拷贝,而引用类型的赋值一般执行的是浅拷贝,当然也有例外,如string等。
35、静态类
静态构造函数,用于初始化类中的静态成员,不能带有参数和访问修饰符,它在第一次调用类成员之前执行,而且只执行一次;它在运行库加载类时执行,而实例构造函数在实例创建时执行;
执行顺序:
分配静态成员的内存空间-》执行静态成员初始化-》执行静态构造函数-》分配对象实例的内存空间-》执行实例成员的初始化-》执行实例构造函数;
静态部分只被执行一次;
静态成员只能有类访问,它在内存中只有一份;实实例成员属于类的实例所有,每new一个对象,都会在内存中分配一块内存区域;
多线程要避免提供改变静态状态的静态方法;
类型构造函数(.cctor)和对象构造器(.ctor)
36、集合:
集中在System.Collection和System.Collections.Generic命名空间;
从实现接口来分,主要分为:有序集合、索引集合和键式集合;
有序集合:主要是指仅实现了ICollection接口的集合类,如Stack(后进先出)和Queue(先进先出);
索引集合:主要是指实现了IList,如Array、ArrayList等;
键式集合:实现IDictionary,如Hashtable;
从集合访问方式来分,分为:列表和字典;
System.Object具有一下特性:
GetType方法、ToString虚方法、MemberwiseClone、GetHashCode、Finalize、Equals、ReferenceEquals和==;
Type.GetType支持运行时跨程序集反射,以解决动态引用;
Typeof运算符只能支持静态引用;
Equals通过虚方法覆写来实现,如果覆写它,又必须得实现GetHashCode方法;==操作符是通过运算符重载来实现;
接口本质上是一个特殊的类,所有方法和属性都是抽象的、支持多继承等,没有继承于Object【原因:从接口单一原则思考,不能为所有接口都赋予GetType等方法;分析IL代码,没有找到object】;
37、序列化和反序列化
序列化:将对象状态转换为可存储或可传输格式的过程。
反序列化:是序列化的逆过程,从物理介质或流上获取数据,并还原为对象的过程。
.NET提供两种强大序列化技术:
1:二进制序列化,是深序列化方式,将对象的成员转换为字节流,进而写入数据流,主要由System.Runtime.Serialization下的类提供;
2:XML序列化,是浅序列化方式,仅仅能序列化对象的public属性和字段,在通过web传输数据的时候,符合xml标准化的开放规则,主要由System.Xml.Serialization下的类提供;
泛型机制中,不再为特定的类型而编码,取而代之的是一种通用的编码方式,因此泛型本质上就是一种代码重用;初次编译时,生成IL和元数据,T只是昨晚类型占位符,不进行泛型类型的实例化;在进行JTT编译时,将以实际类型替换IL代码和元数据中的T占位符,并转换为本地代码;
T为值类型时,JIT编译器为不同的值类型创建不同的本地代码;T为引用类型时,则共享本地代码的单个副本;
约束,指在定义泛型类时,对于能够用于实例化类型参数的类型所做的限制,这种限制能够保证类型参数的合法性;包括:
构造器new()表类型参数必须具有公共无参ctor,多个约束,则位于最后、
值类型struct、引用类型class、基类、接口和裸类约束;
.NET提供两种安全模型:代码访问安全(code-access secutity,CAS)和基于角色的安全(Role-Based security);
CAS用于控制程序的行为,基于证据、安全策略、权限、代码组实现了对代码的安全监控;
Role-Based security用于控制使用者的行为,基于角色安全、用户的身份验证,授权访问特定资源权限,抽象为主体(principals)和标识(Identity)两个基本概念;
引入dynamic动态类型,可以非常容易于com对象交互;DLR为动态语言提供了统一的框架和编程模型;
dynimci源于动态查找,在编译时不做类型判断,而是在运行时进行;
PLINQ并不支持Linq to sql,linq to sql 具体查询并不发生在内存,而是发生在数据库;
协变和逆变属于类型可变性的范畴,是.net为了支持更广泛的隐式类型转换而存在的,实现两个对象间的隐式转换;
IAnyObject可以转换IAnyObject,称为协变,out关键词;反之,为逆变,in关键词;
协变和逆变只对委托和接口有效,对普通的泛型类或者泛型方法无效;
Tuple,元组,函数式编程概念之一,表有序的数据集合;优势:为方法实现多个返回值体验;灵活构建数据结构;
38、什么是递归函数/方法?
任何一个方法既可以调用其他方法也可以调用自己,而当这个方法调用自己时,我们就叫它递归函数或递归方法。
分为两部分:是递归出口和递归本身;
通常递归有两个特点:
1. 递归方法一直会调用自己直到某些条件被满足;
2. 递归方法会有一些参数,而它会把一些新的参数值传递给自己。
39、.NET 如何 处理 异常
如果不 需要 清理 语句 部分, 该 结构 可以 省略 finally 代码 块。 根据 异常 类型 的 不同, 结构 中 可以 有多 个 对应 类型 的 catch 代码 块。.
9、 自定义 异常
编写. NET 程序 中的 自定义 异常, 需要 定义 相应 的 自定义 异常 类, 类 名称 一般以 Exception 结尾。 该类 应 继承 于 System. ApplicationException 类( 为了 区分 于 系统 级 异常), 如果 没有 特殊要求, 类 中 只需 要 定义 构造 函数 即可。
40、 引用 类型 有 哪些 方法 比较 相等 性
System. Object 类 提供 了 三种 方法 测试 引用 类型 是否 相等, 分别为 虚拟 版本 的 Equals 方法、 静态 Equals 方法 和 静态 ReferenceEquals 方法。
(1) 静态方法 的 ReferenceEquals 方法 可接 收 两个 object 参数, 用于 比较 两个 引用 类型 的 引用 地址 是否 相同, 结果 将 返回 true, 否则 返回 false。
(2) 虚拟 的 Equals 方法 是 实例 方法, 由 实例 直接 调用, 接收 一个 object 参数, 同样 用于 比较 引用 类型 的 引用 地址 是否 相等。 不过 其 派生 类 一般 将其 重写 为 比较 对象 的 状态 值, 即 基于 值 的 引用。
说明: 表示 某个 值 或 一组 值 的 任何 类 都应 该 重写 Equals 方法。 如果 类型 要 实现 IComparable 接口,接口, 则 它 应该 重写 Equals 方法。
(3) 静态 的 Equals 方法 和 虚拟 的 Equals 方法 一样, 只是 静态 调用 时 接收 两个 object 参数, 使用 时调 用 实例 Equals 方法。 所以 当 虚拟 Equals 方法 被 重写 为 基于 值 的 相等 比较 时, 该 方法 同样 相同 于 同时 被 重写。 注意: 未 重载 = 运算符 的 引用 类型, = 运算符 用于 比较 两个 引用 类型 的 引用 地址, 跟 引用 类型 的 Equals 方法 一致。 所以 = 运算符 也 被称为 第四 种 判断 相等 性的 方法。
41、字符编码
字符编码 是将 字节 和 字符 的 格式 转换 规范。 国际上 的 通用 字符 编码 一般 为 UTF- 8, 不过 很多 文件 的 字符 编码 采用 操作系统 系统 默认 语言 编码( ANSI), 如 简体 中文 一般 采用 GB2312 编码。 UTF- 8 编码 是 交换 国际 文本 信息 的 主要 方法, 它可 以 支持 世界上 的 所有 语言, 在 实际 应用 中, 为了 使 文件 使用范围 更 广泛, 建议 采用 UTF- 8 编码。
使用 as 或 is 关键字 进行 类型 和 接口 的 运算 即可 得出 该类 型 是否 实现 指定 的 接口。
41、如何 对 集合 类型 进行 排序
对 集合 类型 进行 排序 必须 实现. NET 基 类 库 的 接口 完成, 例如 对于 集合 子项 为 内置 类型 时, 直接 调用 Sort 静态 方法 即可 排序。 而 对于 自定义 类型 的 集合 子项, 该类 型 必须 实现 System. IComparable 接口, 如果 需要 进行 多个 键值 排序, 必须 定义 辅助 类, 实现 System. Collections. IComparer 接口。
42、 栈 集合 和 队列 集合 有 什么 区别
栈 集合 和 队列 集合 都是 System. Collections 命名 空间 下 的 集合 类型。Stack 类 也 被称为 栈 集合 类型, Queue 类 也 被称为 栈 队列 集合 类型。 顾名思义, 栈 集合 类型 的 子项 保存 方式 为 先入 后 出 的 方式, 而 队列 集合 类型 则 相反, 子项 保存 为 先入 先出 方式。
43、C# 函数 的 参数 修饰 符 有 如下 4 种 情况:
(1) 无 参数 修饰 符, 表示 参数 传递 方式 为 按 值 传递。
(2) out 修饰 符, 表示 参数 传递 方式 为 按 引用 传递, 参数 传递 前 不需要 赋 初始 值, 并且 被 调用 函数 必须 给 该 参数 赋值。
(3) params 函数 签名 中 只 允许 一个 params 修饰 符, 并且 被 修饰 参数 应为 最后 一个 修饰 符, 可将 不确定 个数 的 一组 同类型 参数 作为 一个 逻辑 参数 传递, 参数。
(4) ref 修饰 符, 表示 参数 传递 方式 为 按 引用 传递, 调用 前 需要 赋 初始 值, 被 调用 函数 可以 不对 参数 重新 赋值。
44、 什么是装箱(boxing)和拆箱(unboxing)?
装箱:从值类型接口转换到引用类型。
拆箱:从引用类型转换到值类型。
第二章 高级语法
1、实现继承和接口继承:
子类不光继承父类的公有成员,同时继承父类的私有成员,只是在子类中不被访问;
1、抽象类适合于有族层概念的类间关系,而接口最适合为不同的类提供通用功能;
2、接口着重于can-do关系,抽象类偏重于is -a 关系;
3、接口多定义对象的行为;抽象类多定义对象的属性;
4、如果预计会出现版本问题,创建“抽象类”,例如:dogchickenduck,那么考虑抽象出animal,来应对以后出现马和牛的事情;而向接口添加新成员则会强制要求修改所有派生类,并重新编译;
抽象类和接口异同:
相同:
都不能被直接实例化,可以通过继承实现其抽象方法;
都是面向抽象编程的技术基础,通过封装变化来实现实体之间的关系;
不同点:
接口支持多继承;抽象类不能;
接口只能定义抽象规则;抽象类即可以定义规则,还可以提供已实现成员;
接口着重于can-do关系,抽象类偏重于is -a 关系;
抽象类应主要用于关系密切的对象,而接口最合适为不相关的类提供通用功能,尽量将接口设计成功能单一的功能块;
2、聚合还是继承?
类与类之间关系,继承、聚合(是一种has -a 关系,耦合度低些)、依赖;
继承的缺点:
1、继承可能造成子类的无限膨胀,不利于类体系的维护,甚至出现无效的继承;
2、继承的子类对象确定于编译期,无法满足运行期才确定的情况,而类聚合很好解决了这个问题;
面向对象基本原则:多聚合、少继承;低耦合、高内聚;
以adapter模式的两种方式为例比较继承和聚合的适应场合与柔性较量;
adapter主要用于将一个类的接口转换为另外一个接口,通常情况下在不改变原有体系的条件下应对新的需求变化,通过引入新的适配器类来完成对既存体系的扩展和改造。adapter模式主要包括:
类的adapter模式:通过引入新的类型来继承原有类型,同时实现新加入的接口方法。缺点耦合度较高,引入过多的新类型;
对象的adapter模式:通过聚合而非继承的方式来实现对原有系统的扩展,松散耦合,引入较少的新类型;
从面向对象的角度来看,可分为基类继承式多态和接口实现式多态;
通过读取配置文件的相关信息可以很容易创建具体的对象,体现了面向接口编程的另一个好处:对修改封闭而对扩展开放;
3、泛 型 有 什么 优势
类型 在 定义 时, 数据 类型 由 T 代替, 只是 在 创建 对象 时 指定 明确 的 数据 类型, 这样 不仅 保证 了 类型 安全( 类型 错误 时 无法 通过 编译), 而且 不会有 多余 的 装箱 和 拆 箱 操作。
泛 型 可以 灵活 定义 数据 类型 而 省去 了 多余 的 装箱 和 拆 箱 操作, 并在 编译 时 执行 强制 类型 检查 以 保证 类型 的 安全。
14、 如何 使用 泛 型 链 表
泛 型 链 表 即 LinkedList< T>, 泛 型 链 表 是一 种 双向 链 表, 链 表中 的 节点( 子项) 都 包含 了 前一 节点( 子项) 和 后 一 节点( 子项) 的 指针。
4、简述.NET框架中的反射
反射是一种动态分析程序集、模块、类型、字段等目标对象的机制,它的实现依托于元数据。在CLR中元数据就是对一个模块定义或引用的所有东西的描述系统。
15、泛型约束
多个 约束 可以 用 空格 分隔 后 一起 使用, 约束 种类 如以 下 代码 所示:
//类型 参数 T 仅 允许 直接 或 间接 派 生于 System. ValueType 类型
where T : struct
//类型 参数 T 必须 为 引用 类型
where T : class
//类型 参数 T 必须 有 1 个 默认 的 构造 函数, 该 约束 应 位于 约束 列表 的 末尾
where T : new()
//类型 参数 T 必须 派生 于 指定 类型
where T : 指定 类 名称
//类型 参数 T 必须 实现 指定 接口 类型
where T : 指定 接口 名称
16、 委托
CLR 能够 保证 委托 指向 一个 有效 的 方法, 不会 指向 无效 地址 或者 越界 地址。
读者 可将 委托 看作 一种 新的 对象 类型, 委托 主要 用于. NET Framework 中的 事件 处理 程序 和 回 调 函数。 委托 类型 可定义 一个 签名, 并且 它 只能 持有 与 它的 签名 相 匹配 的 方法 的 引用。
17、编写 简单 的 事件 机制 实例
而在 C# 中 是由 多点 委托 和 事件 来 实现 这种 机制 的。 这种 设计 模式 可以 称为 发布者/ 订阅 者 模式, 发布者 发布 事件, 多个 类 订阅 这个 事件( 类 必须 包含 一个 相应 的 事件 处理 方法)。 当 该 事件 被 产生( 触发), 系统 通知 每个 订阅 者 该 事件 发生。 触发 事件 所 调用 的 事件 处理 方法 由 委托 来 实现。
这种 情况下, 必须 主意 以下 几点:
(1) 委托 类型 的 签名 必须 有两 个 参数, 分别 是 触发 事件 的 对象 和 事件 信息。
(2) 事件 信息 必须 是由 EventArgs 类 派生。 这样 编程 者 在 写 触发 事件 的 对象 类 时不 必 知道 事件 信息 对象 类, 反之亦然。 而且 事件 信息 对象 类 可以 在 运行时 订阅 或 解除 订阅 特定 的 事件。 简单 地说, 事件 就是 当 对象 或 类( 发布者) 状态 发生 改变 时, 对象 或 类 发出 的 信息 或 通知 订阅 者, 发布者 也 被称为“ 事件 源”。
编写 简单 的 事件 机制 需要 先 定义 委托 类型, 然后 通过 委托 类型 定义 事件, 最后 事件 处理 方法 订阅 事件。 假设 定义 了 名为 MyDel 委托 类型, 事件 名称 为 onclick, 定义 部分 如以 下 代码 所示:
public delegate 返回 类型 MyDel( object sender, EventArgs e);
public event MyDel onclick;
MyDel 委托 对象 名称 += new MyDel( 事件 处理 方法);
//事件 处理 方法 订阅 onclick 事件
onclick += 委托 对象 名称;
//事件 处理 方法 取消 订阅 onclick 事件
onclick -= 委托 对象 名称;
从 以上 代码 可 看出, 事件 实质上 是一 种 特殊 的 委托, 并且 事件 通过 多点 委托 的 方法 被 多个 方法 订阅。 当事 件 触发 时, 相应 的 事件 处理 方法 将会 被 引用。
18、匿名方法
C# 的 匿名 方法 可以 使 委托 及 事件 的 代码 编写 更加 精简、 高效。 一般 情况下, 事件 注册 事件 处理 方法, 需要 首先 定义 这个 方法, 如果 该 方法 仅用 于 订阅 特定 的 事件, 则 可以 使用 使 代码 更加 简明 的 匿名 方法。
Lambda 表达式 将 delegate 都 隐藏 了, 大大 简化 了 代码 的 编写。 Lambda 表达式 可以 完全 代替 匿名 方法。
19、反射 技术
反射 技术 可以 在 运行时 获取 程序 集中 每个 类型 的 成员, 包括 字段、 方法、 属性、 事件 等, 并进 一步 获取 这些 成员 的 详细信息。 反射 技术 还可 动态 载入 外部 程序 集( 私有 程序 集 或 共享 程序 集), 并 获取 程序 集中 类型 的 相关 数据。
它是在运行时创建和使用类型实例。
20、特性
只需 在 类 或 成员 前面 注释[ Obsolete] 特性, 一旦 主 程序 中 使用 了 该类 或 成员, 可以 在 编译 时 给出 警告。
自定义 特性 需 定义 一个 System. Attribute 类 的 派生 类, 才可 以在 程序 中 使用。
22、序列 化
顾名思义, 反 序列 化 即为 序列 化 的 逆向 操作, 既然 序列 化 操作 需 写入 到 流 中, 反 序列 化 即 读取 流 并将 对象 数据 提取 出来。
4.IEnumerable还是IQueryable的区别
IQueryable继承自IEnumerable,所以对于数据遍历来说,它们没有区别。
但是IQueryable的优势是它有表达式树,所有对于IQueryable的过滤,排序等操作,都会先缓存到表达式树中,只有当真正遍历发生的时候,才会将表达式树由IQueryProvider执行获取数据操作。
而使用IEnumerable,所有对于IEnumerable的过滤,排序等操作,都是在内存中发生的。也就是说数据已经从数据库中获取到了内存中,只是在内存中进行过滤和排序操作。
对比能够发现,使用IQueryable的查询,Take(2)的操作是通过Sql在数据库中完成的。
延迟加载,用IQueryable接口集合的时候,只有调用变量时候,才去真正查询数据库;缺点:如果多次调用变量的话,就多次查询数据库;
(3)操作本地数据源用IEnumerable,操作远程数据源用IQueryable
IQueryable是负责生成SQL语句的,但并不马上执行;而IEnumerable是对任意类型的集合都能操作的,不限于是数据库还是一般的Array还是List。
23、事务
.NET Framework中也有TransactionScope类,它的行为和.NET Core中的TransactionScope类的行为类似,不过.NET Framework中的TransactionScope类会使用Windows特有的MSDTC(Microsoft distributed transaction coordinator,微软分布式事务调解器)技术支持分布式事务,而.NET Core由于是跨平台的,因此不支持分布式事务。MSDTC实现的分布式事务是强一致性的事务,尽管很简单易用,但是会带来性能等问题,不符合现在主流的“最终一致性事务”方案。因此,我们在.NET Core中使用TransactionScope的时候不用担心MSDTC事务提升的问题,当需要进行分布式事务处理的时候,请使用最终一致性事务。
7、 泛型和委托
委托使方法参数化,而泛型实现类型参数化。
24:请解释包含/委托模型
包含/委托模型描述了类关系的一种“has a”:B has a A,即B的实例中有一个A类型的成员变量。“has a”这种模型是继承另一种重要模型。
面向对象中接口表示“…像…”(like a关系);继承表示“…是…”(is a关系);而包含/委托组合模式表示“…有…”(has a关系)。
27:如何应用.NET中的委托
委托是一类继承自 System.Delegate 的类型,每个委托对象至少包含了一个指向某个方法的指针,该方法可以是实例方法,也可以是静态方法。委托实现了回调方法的机制,能够帮助程序员设计更加简单优美的面向对象程序。
29:什么是多播委托
多播委托是指一个由委托串成的链表,当链表上的一个委托被回调时,所有链表上该委托的后续委托将会被顺序执行。但要注意,多播委托必须是同类型的,返回类型必须为void,并且不能带输出参数(但可以带引用参数)。
30:简述.NET中的事件
事件是.NET开发人员经常使用的一个机制,事件是一种使对象或类能够提供通知的成员。客户端可以通过提供事件处理程序为相应的事件添加可执行代码。事件可以理解为一种特殊的委托。
54、您在什么情况下会用到虚方法?它与接口有什么不同?
答案:子类重新定义父类的某一个方法时,必须把父类的方法定义为virtual
在定义接口中不能有方法体,虚方法可以。
实现时,子类可以不重新定义虚方法,但如果一个类继承接口,那必须实现这个接口。
第三章、面向对象
面向对象编程
面向对象编程(object-oriented programming,oop)技术:就是使用对象;
对象的生命周期:构造阶段(对象最初进行实例化的时期,用于初始化数据的函数)和析构阶段(在删除一个对象是,执行一些清理工作);
面向对象的基本特征: 抽象、接口、继承、多态性、封装;
面向对象的重点在于谁发出了什么命令,而面向过程只关心一个命令.
面向对象以对象为基础,以事件或消息驱动对象执行处理.它不像面向过程设计一样以函数为单元
面向对象分析(OOA)、面向对象设计(OOD)、面向对象实现(OOP):
OOD设计准则:模块化、抽象、高内聚低耦合、可重用;
oop的3个核心:封装(隐藏一个对象内部实现并保护数据完整性)、继承(促进代码重用)、多态();
类型之间有种“is-a”关系,称为经典继承关系;
Object.Equals()只有当被比较的两个对象引用内存中相同的对象实例时才会返回true;
13:如何理解多态特性
多态则可简单地概括为“一个接口,多种方法”,它是在程序运行的过程中才决定调用的方法。多态是一种概念,也是一种思想。重载(overload)和重写(override)都是多态的体现。
而关于多态,人的世界中,我们常常在不同的环境中表现为不同的角色,并且遵守不同的规则。
对象中的多态主要包括以下两种情况:
·接口实现多态,就像上例所示。
·抽象类实现多态,就是以抽象类来实现。
26:接口和抽象类的区别
相同点:
都不能被直接实例化,都可以通过继承实现其抽象方法。
不同点:
接口支持多继承;抽象类不能实现多继承。
接口只能定义行为;抽象类既可以定义行为,还可能提供实现。
接口可以用于支持回调(CallBack);抽象类不能实现回调,因为继承不支持。
接口只包含方法(Method)、属性(Property)、索引器(Index)、事件(Event)的签名,但不能定义字段和包含实现的方法;
抽象类可以定义字段、属性、包含有实现的方法。
接口可以作用于值类型(Struct)和引用类型(Class);抽象类只能作用于引用类型。例如,Struct就可以继承接口,而不能继承类。
10:类与结构有什么区别
主要体现在3个方面:结构是值类型,而类是引用类型;结构不能被继承而类可以;结构与类的内部结构不同。
36、 C#中的接口和类有什么异同。
不同点:
不能直接实例化接口。
接口不包含方法的实现。
接口可以多继承,类只能单继承。
类定义可在不同的源文件之间进行拆分。
相同点:
接口、类和结构都可以从多个接口继承。
接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。
接口和类都可以包含事件、索引器、方法和属性。
发表评论