c#基础:
1、请介绍关键字internal、sealed、readonly、static、is、as、params、this。请结合实际场景说明你在项目中的使用情况。
(1) internal 访问修饰符 只有在同一程序集的文件中,内部类型或成员才是可访问的,内部访问通常用于基于组件的开发,因为它使一组组件能够以私有方式进行合作,而不必向应用程序代码的其余部分公开。
(2) sealed 修饰符可以应用于类、实例方法和属性。密封类不能被继承。密封方法会重写基类中的方法,但其本身不能在任何派生类中进一步重写。当应用于方法或属性时,sealed 修饰符必须始终与 override一起使用
(3) readonly 字段可以在声明或构造函数中初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。const 字段是编译时常数,常数表达式是在编译时被完全计算出来,它只能在该字段的声明中初始化. const 默认就是静态的,而 readonly 如果设置成静态的就必须显示声明。
(4) static 修饰符声明属于类型本身而不是属于特定对象的静态成员,尽管类的实例包含该类所有实例字段的单独副本,但每个静态字段只有一个副本。不可以使用 来引用静态方法或属性访问器。如果对类应用 static 关键字,则该类的所有成员都必须是静态的。
(5) abstract 修饰符指示所修饰的内容缺少实现或未完全实现。 abstract 修饰符可用于类、方法、属性、索引器和事件。 标记该类为抽象类,抽象方法只能由派生类实现,是一个隐式的虚方法。
(6) is 检查对象是否与给定类型兼容。 例如,if (obj is MyObject) 的代码可以确定对象是否为 MyObject 类型的一个实例,或者对象是否为从 MyObject 派生的一个类型。请注意,is 运算符只考虑引用转换、装箱转换和取消装箱转换。 不考虑其他转换,如用户定义的转换。在 is 运算符的左侧不允许使用匿名方法。 lambda 表达式属于例外
(7) as用于在兼容的引用类型之间执行转换,as 运算符类似于强制转换操作;但是,如果转换不可行,as 会返回 null 而不是引发异常。更严格地说,这种形式的表达式 就相当于 obj as type===>obj is type ? (type)obj : (type)null
as 运算符只执行引用转换和装箱转换。as 运算符无法执行其他转换,如用户定义的转换,这类转换应使用 cast 表达式来执行。
(8) this 关键字引用类的当前实例(对象),还可用作扩展方法的第一个参数的修饰符。由于静态成员函数存在于类一级,并且不是对象的一部分,因此没有 this 指针。 在静态方法中引用 this 是错误的。
this可以限定被相似的名称隐藏的成员,例如当前实例有变量name,有一个方法的参数也是name,那么此时this.name就是指当前实例的成员,而不是只参数,起到一个区分的作用。This也可以将对象作为参数传递到其他方法
(9) params 关键字可以指定在参数数目可变处采用参数的方法参数。在方法声明中的 params 关键字之后不允许任何其他参数,并且在方法声明中只允许一个 params 关键字。public static void UseParams2(params object[] list){}
2 ref 和out
关键字使参数按引用传递(参数默认是按照值传递的)。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref或out参数,则方法定义和调用方法都必须显式使用 ref 和out关键字。传递到 ref 参数的参数必须最先初始化。这与 out 不同,out 的参数在传递之前不需要显式初始化
3 delegate委托 ,事件
是一种可用于封装命名或匿名方法的引用类型,通过将委托与命名方法或匿名方法关联,可以实例化委托。 必须使用具有兼容返回类型和输入参数的方法或 lambda 表达式实例化委托
delegate void testDelegateMes 声明了一个委托 可以理解为一个特殊的类
testDelegateMes tdm1=new testDelegateMes(showMes);
testDelegateMes tdm1 //可以理解为声明了该类的一个变量
new testDelegateMes(showMes);//将委托与命名方法或匿名方法关联,此时tdm1就等价于showmes方法,一个委托可以搭载多个方法
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。所有委托都有一个构造器默认获取两个参数,一个是对象引用@object,一个是引用了回调方法的整数method;在构造器内部这两个参数被保存在_target和_methodPtr私有字段中,编译器在定义委托类的时候定义了一个Invoke方法,在Invoke被调用时(默认使用委托时,其实是调用了该委托的Invoke方法),它使用私有字段_target和_methodPtr在指定对象上调用包装好的回调方法(委托代替方法执行的原理)。(invoke方法的签名和委托的签名匹配)
委托的属性表现形式就是事件,利用委托的多播特性可以将多个行为与委托绑定。
4 请说一下静态构造函数和默认构造函数的区别
静态构造函数同其他静态成员一样是属于类的,它用来初始化一些静态成员。静态构造函数只会被执行一次。也就是在创建第一个实例或引用任何静态成员之前,由.NET自动调用。也就是说我们无法直接调用静态构造函数。如果没有写静态构造函数,而类中包含带有初始值设定的静态成员,那么编译器会自动生成默认的静态构造函数。一个类只能有一个静态构造函数。
5 请说一下属性和字段的区别。
属性有比字段更大的灵活性和可扩展性,字段”是直接在类或结构中声明的任何类型的变量。属性提供灵活的机制来读取、编写或计算某个私有字段的值。 可以像使用公共数据成员一样使用属性,但实际上它们是称作“访问器”的特殊方法。有助于提高方法的安全性和灵活性。对于属性,你可以自定义取值赋值的操作。可以只读也可以只写,而字段始终是可读可写的(字段一般属于类私有的,在外部访问他破坏了类的封装型)
6 什么是堆,栈, 值类型, 引用类型 ,装修, 拆箱
托管堆和线程栈是内存上的两块存储区域,应用程序的每个线程都会被分配1m的存储空间作为线程栈,用于存储自身的数据(局部变量,实参等)这块内存区域的分配和回收不受应用程序干扰,当一个变量离开其作用域的时候内存空间立即被释放。栈在内存中是由高内存地址往低内存地址填充,释放是由低往高释放(先进后出保证了不与变量的声明周期起冲突)。栈中存储值类型
另一块内存区域称为“堆(heap)”,在.NET 这种托管环境下,堆由CLR 进行管理,所以又称为“托管堆(managed heap)”。 托管堆存储引用类型,类是引用类型,用new关键词创建的对象分配的内存空间位于托管堆上。当对象使用完被垃圾回收机制回收时,空间会被回收 与栈不同,堆是从下往上分配,所以自由的空间都在已用空间的上面
通用类型系统(CTS)区分两种基本类型:值类型和引用类型。它们之间的根本区别在于它们在内存中的存储方式。值类型存储于栈中,引用类型存储在堆中。值类型比引用类型轻。原因是它不作为对象在托管堆中分配,不被垃圾回收,也不通过指针进引用。没有堆上每个对象成员都有的额外成员:(类型对象指针和同步块索引),当这个值类型离开作用域时,所占空间即被释放
string是特殊的引用类型,字符串代表一个固定不变的Unicode字符序列(类似于不变集合一样,每当改变都不是针对原来的对象的改变,而是产生的一个新的对象)。这种不变性意味着,一旦在堆中分配了一个字符串,它的值将永远不会改变。如果值改变了,.NET就创建一个全新的String对象,并把它赋值给该变量。
装箱:
值类型转化为引用类型要使用装箱机制,装箱分三步:
- 在托管堆中分配内存,分配的内存量是值类型各字段所需的内存量,还要加上托管堆上所有对象都有的两个成员(类型对象指针和同步块索引)
- 值类型的字段复制到新分配的堆内存
- 返回对象地址,现在该地址是对象引用。值类型成了引用类型。
拆箱:
将一个引用类型转化为值类型的时候,要拆箱。值类型转为引用类型分2步:
- 获取已装箱的对象(此时为引用类型)的各个成员的地址;叫拆箱
- 将字段包含的值从堆中复制到基于栈的值类型实例中
拆箱不是将装箱过程倒过来,拆箱的代价要比装箱低很多,拆箱就是获取指针的过程,该指针指向一个对象中的原始值类型(数据字段).紧接着拆箱往往会发生一次字段复制
发生装箱和拆箱的几种情况:
1 发生值类型转为引用类型的情况 会装箱
2 类型在调用tostring方法时,内部并未重写该虚方法,而是使用的基类(object)得tostring方法,会装箱,在重写了基类tostring方法的时候,是用base.tostring一样会发生装箱
3 是用gettype 同上,在值类型的派生类未重写基类(object)得方法时一样会发生装箱
4 将值类型的某个实例转换为类型的某个接口时,要对实例进行装箱。这是因为接口变量必须包含对堆对象的引用
7 CLR垃圾回收机制
clr要求所有对象都从托管堆分配,当使用new操作符创建一个对象的时候会在堆上分配一块空间,并返回指向这块存储区域的指针(引用地址),当托管堆没有足够的空间的时候会执行垃圾回收。在执行垃圾回收时,
1 clr会暂停所有线程,
2 并标记所有对象是否是可达的(是否还有根(变量)引用它),不可达对象将被执行回收
3 之后进入压缩阶段,将所有可达的对象执行压缩,使他们占用连续的堆内存空间(好处是:1所有对象内存中紧挨在一起,恢复了引用的局部化,减小了应用程序的工作集,提升了性能。2可用空间是连续的,允许其他对象入住。3压缩意味着解决了原生堆空间碎片化问题)。同时,clr还要从每个根减去所引用的对象在内存中偏移的字节数,这样就能保证每个根还是引用和之前一样的对象。
4 clr恢复应用程序所有线程
简单来说整个过程分五步:暂停进程的所有线程——>将所有对象的同步块索引位设置为0——>检查所有根引用的对象,并标记其同步块索引位1,没有被根引用的对象保持原来的0不被重新标记——>删除标记为0的对象,执行压缩,并根据对象在内存中偏移的字节数调整根的引用地址——>clr恢复暂停的线程
clr的GC是基于代的垃圾回收器
clr的代一共分为3代:0,1,2;为每一代都分配了预算容量(KB),这个预算容量是根据回收的情况进行动态调整的,如果一次对第0代垃圾回收的对象较少,说明可用对象较多,则会增大对第一代预算,相反则相应减少容量预算。
对象被创建时未第0代对象,当0代存储空间满后,执行一次gc后幸存的对象升级为第1代对象,之后在0代空间执行第2次gc后幸存的对象继续升级为第一代对象,直到第1代存储空间也满了,在执行gc时,会同时回收第0代和第一代的对象(此时1代空间的对象很可能已经有不可达对象等待被回收),这时原来存储在第1代空间的幸存对象会被提升未第2代。第0代幸存的提升为第1代。当所有存储空间都满了的时候,就内存溢出了。
垃圾回收的触发条件:
- clr在检测第0代超过预算时
- 代码显示调用system.GC的静态方法Collect,强制垃圾回收
- window报告低内存时
- clr正在卸载AppDomain时,clr认为一切都不是根,执行涵盖所有代码的垃圾回收
- 进程结束时,clr正常关闭,windows将回收进程的所有内存
8 深拷贝和浅拷贝
深拷贝和浅拷贝之间的区别在于是否复制了子对象。浅拷贝(影子克隆/shallow copy):只复制对象的值类型字段,对象的引用类型,仍属于原来的引用. 深拷贝(深度克隆):不仅复制对象的值类型字段,同时也复制原对象中的对象.就是说完全是新对象产生的。例如一个学校类里不仅包含了schoolname属性,还包含了students对象的引用,此时浅拷贝就是会创建schoolname的副本和对students对象指针的副本(并不会创建一个全新的students对象),而深拷贝则会创建一个全新的students对象副本出来
9 请说明Equals和==的区别。
C#中的相等有两种类型:引用相等(ReferenceEquals)和值相等(Equals)。值相等就是说两个对象包含相同的值。而引用相等则比较的是两个对象的引用是否是同一个对象。也就是说,如果ReferenceEquals为True,则Equals必然为True,反过来就不一定了
==运算符(引用相等):通过过判断两个引用是否指示同一对象来测试是否相等。
Equals是要实现值相等比较的,为object类的方法,一个类默认情况下,在不重写基类Equals方法的时候对比的是对象的引用是否指向同一块内存地址。重写Equals函数的目的是为了比较两个对象的value值是否相同,例如利用equals比较八大包装对象(如int,float等)和String类(因为该类已重写了equals和hashcode方法)对象时,默认比较的是值,在比较其它自定义对象时都是比较的引用地址(例如自定义的类,因为没有重写equals方法)。
如何重写equals方法:
主要用于通过对比自定义类中的每个成员来对比两个类是否值相等;重写equals也要重写hashcode方法
在创建1个对象的时候会自动为这个对象创建hashcode,这样如果我们对2个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,此时在未重写hashcode方法的时候,这两个对象却拥有不同的hashcode,由此将产生了理解的不一致,在存储散列集合时(如Set类),将会存储了两个值一样的对象,导致混淆,因此,就也需要重写hashcode()
hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的,在这里对比两个对象相同时,其中最先对比的是对象的hashcode,如果两个对象的hashcode不同,则这两个对象必然不同,从而大大提高了效率
10 面向对象三大特征:
封装:封装就是将数据或函数等集合在一个个的类中,被封装的对象通常被称为抽象数据类型。封装的意义在于保护或者防止代码(数据)被我们无意中破坏。例如我们会将属于类本身的行为,字段或方法定义为private,外部不能能访问它。使用属性来控制对数据字段的修改或只读只写。访问修饰符:Private,Protected(类和派生类可以存取),Internal(只有同一个项目中的类可以存取),Public
继承:继承主要实现代码间的纵向联系,继承了父类的子类也会有父类的行为。
1、C#中的继承符合下列规则:
- 继承是可传递的。如果C从B中派生,B又从A中派生,那么C不仅继承了B中声明的成员,同样也继承了A中的成员。Object类作为所有类的基类。
- 派生类应当是对基类的扩展。派生类可以添加新的成员,但不能除去已经继承的成员的定义。
- 构造函数和析构函数不能被继承。除此之外的其它成员,不论对它们定义了怎样的访问方式,都能被继承。基类中成员的访问方式只能决定派生类能否访问它们。
- 派生类如果定义了与继承而来的成员同名的新成员,就可以覆盖已继承的成员。但这并不因为这派生类删除了这些成员,只是不能再访问这些成员。
- 类可以定义虚文法、虚属性以及虚索引指示器,它的派生类能够重载这些成员,从而实现类可以展示出多态性。
2、new关键字
如果父类中声明了一个没有friend修饰的protected或public方法,子类中也声明了同名的方法。则用new可以隐藏父类中的方法。(不建议使用)
3、base关键字 base 关键字用于从派生类中访问基类的成员:
- 调用基类上已被其他方法重写的方法。
- 指定创建派生类实例时应调用的基类构造函数。
多态:(重载就是多态)
编译时的多态性:
编译时的多态性是通过重载来实现的。对于非虚的成员来说,系统在编译时,根据传递的参数、返回的类型等信息决定实现何种操作。
运行时的多态性:运行时的多态性就是指直到系统运行时,才根据实际情况决定实现何种操作。C#中,运行时的多态性通过虚成员实现。编译时的多态性为我们提供了运行速度快的特点,而运行时的多态性则带来了高度灵活和抽象的特点。
2、实现多态:接口多态性。继承多态性。通过抽象类实现的多态性。
3、override关键字:重写父类中的virtual修饰的方法,实现多态。
11 请谈一下你对Exception的理解
在 C# 中,程序中的运行时错误通过使用一种称为“异常”的机制在程序中传播。异常由遇到错误的代码引发,由能够更正错误的代码(try catch)捕捉。 一旦引发了一个异常,clr自上而下搜索匹配的catch块,一个try块对应多个catch块,越具体的catch应该在上面,接着是她们的基类型,最后是exception类,未指定也是exception类型。如果上下顺序反了,那么具体的catch块会执行不到,编译器会报错。try catch 以后,如果只是处理了捕获的异常(例如记日志),没有抛出异常(throw),后续代码会继续执行
throw是抛出一个异常,中断执行后续代码,如果一个方法可能会有异常,但你不想处理这个异常,就是用throw,谁调用了这个方法谁就要处理这个异常,或者继续抛出。
throw和throw ex
1 throw ex 抛出与catch捕捉到的相同的异常对象,导致clr重置该异常的起点。认为你catch到的异常已经被处理了,只不过处理过程中又抛出新的异常,从而找不到真正的错误源 try{ }catch (Exception ex){ throw ex; }
2 throw 重新抛出异常对象,clr不会重置起点(推荐使用这个)try{} catch{ throw;}
3 或者对异常就行重新包装,保留原始异常点信息,然后抛出
finally:
无论代码有没有抛出异常,finally块始终会执行。应该先用finally块清理那些已经成功的操作,还可以用finally显示释放对象避免资源泄露。Finally 块使程序员能够清除中止的 try 块可能遗留下的任何模糊状态,或者释放任何外部资源(例如图形句柄、数据库连接或文件流),而无需等待运行时中的垃圾回收器终结这些对象.
使用了 lock,using,foreach的语句,c#编译器会自动加上try finally块,并在finally中自动释放资源
可以在一个线程中补获异常,在另外一个线程中重新抛出异常;
clr在调用栈中向上查找与抛出的异常对象类型匹配的catch块,没有任何catch块匹配抛出的异常类型,就会发生一个未处理的异常
12、你平时常用的集合类有哪些? 分别有什么区别?请结合实际场景说明你在项目中的使用情况。
System.Collections命名空间包含各种集合对象和接口的定义(如列表、队列、位数组、哈希表和字典)System.Collections.Generic 命名空间包含定义泛型集合的接口和类,泛型集合允许用户创建强类型集合,它能提供比非泛型强类型集合更好的类型安全性和性能。
数组、arraylist、list、hashtable、directonary、stack堆栈、quene队列
数组:数组是固定大小的,不能伸缩,数组要声明元素的类型
a ArrayList动态数组可以存储任何类型,不像List泛型那样有严格要求,ArrayList就相当于是list<object>,List类在大多数情况下执行得更好并且是类型安全的。注意list非线程安全的,多线程中操作一个list最好使用ConcurrentList,和ConcurrentDictionary一样内部自带加速机制
b HashTable应用场合:做对象缓存,树递归算法的替代,和各种需提升效率的场合。HashTable中的key/value均为object类型,由包含集合元素的存储桶组成。HashTable的优点就在于其索引的方式,速度非常快。如果以任意类型键值访问其中元素会快于其他集合,特别是当数据量特别大的时候,效率差别尤其大。
c Hashtable和Dictionary<K, V>类型
1:单线程中推荐使用Dictionary,有泛型优势,且读取速度较快,容量利用更充分.
2:多线程中推荐使用Hashtable,默认Hashtable允许单线程写入, 多线程读取对Hashtable进一步调用Synchronized()方法可以获得完全线程安全的类型,而Dictionary非线程安全,必须人为使用lock语句进行保护, 效率大减。 .net提供了另外一个字典类ConcurrentDictionary,是线程安全的支持并发操作
3:Dictionary有按插入顺序排列数据的特性(注:但当调用Remove()删除过节点后顺序被打乱), 因此在需要体现顺序的情境中使用Dictionary能获得一定方便。
d HashTable、HashMap、HashSet之间的区别
HashTable是基于Dictionary的key value集合,是线程安全的
HashMap是基于Map接口的一个实现,HashMap可以将空值作为一个表的条目的key或者value,HashMap中由于键不能重复,因此只有一条记录的Key可以是空值,而value可以有多个为空,但HashTable不允许null值(键与值均不行)
HashSet 是set的一个实现类,存储的是一个对象列表,底层采用的是HashMap进行实现的,但是没有key-value,只有HashMap的key set的视图,HashSet不容许重复的对象
13、什么是接口,什么是抽象类?二者有什么区别?请结合实际场景说明你在项目中的使用情况。
抽象类和接口都是对业务的一种抽象方式。
1、接口定义了一种行为,一种规则,它告诉了你要做什么,但不会去具体实现。当一个类继承了这个接口的时候,它将负责实现这个接口
2、当类选择实现某个接口的时候,类必须将这个接口全部实现,不能只实现其中一部分。
3、接口和抽象类的区别:一个类只能继承一个父类,但一个类可以同时实现多个接口,格式为: 类名:接口1,接口2
4、接口里的成员方法(行为),没有修饰符,但接口本身是可以有修饰符的
5、抽象类的作用:负责定义“实现和部分的动作”(抽象类中可以实现某些方法),接口只负责定义动作不负责实现
interface的应用场合:
- 类与类之前需要特定的接口进行协调,而不在乎其如何实现。
- 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识。
- 需要将一组类视为单一的类,而调用者只通过接口来与这组类发生联系。
- 需要实现特定的多项功能,而这些功能之间可能完全没有任何联系。
abstract class的应用场合:一句话,在既需要统一的接口,又需要实例变量或缺省的方法的情况下,就可以使用它。最常见的有:
- 定义了一组接口,但又不想强迫每个实现类都必须实现所有的接口。可以用abstract class定义一组方法体,甚至可以是空方法体,然后由子类选择自己所感兴趣的方法来覆盖。
- 某些场合下,只靠纯粹的接口不能满足类与类之间的协调,还必需类中表示状态的变量来区别不同的关系。abstract的中介作用可以很好地满足这一点。
- 规范了一组相互协调的方法,其中一些方法是共同的,与状态无关的,可以共享的,无需子类分别实现;而另一些方法却需要各个子类根据自己特定的状态来实现特定的功能
典型的是包含主订单和子订单的系统中应该使用抽象类,因为他们有共享的业务逻辑不需要子订单类实现,同时也有各子订单必须实现订单的操作约定
14、什么是进程,什么是线程,什么是AppDomain?三者有什么区别?请结合实际场景说明你在项目中的使用情况。
答:进程是系统进行资源分配和调度的单位;线程是CPU调度和分派的单位,一个进程可以有多个线程,这些线程共享这个进程的资源。
AppDomain表示应用程序域,它是一个应用程序在其中执行的独立环境。是CLR的运行单元,它可以加载Assembly、创建对象以及执行程序。AppDomain是CLR实现代码隔离的基本机制。AppDomain被创建在进程中,一个进程内可以有多个AppDomain。一个AppDomain只能属于一个进程。AppDomain是个静态概念,只是限定了对象的边界;线程是个动态概念,它可以运行在不同的AppDomain。一个AppDomain内可以创建多个线程,但是不能限定这些线程只能在本AppDomain内执行代码。每个appdomain都有一个托管堆,每个托管堆内有两块区域,一块存储对象,一块存储对象元数据(静态成员)。
15、请谈一下你对泛型的理解。请结合实际场景说明你在项目中的使用情况。
1、所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型,泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更灵活的复用。 C#泛型赋予了代码更强的类型安全,更好的服用,更高的效率,更清晰的约束。
泛型的意义何在?类型安全和减少装箱、拆箱并不是泛型的意义,而是泛型带来的两个好处而已(或许在.net泛型中,这就是明显的好处) 泛型的意义在于—把类型作为参数,它实现了代码见的很好的横向联系,我们知道继承为了代码提供了一种从上往下的纵向联系, 但泛型提供了方便的横向联系(从某种程度上说,它和AOP在思想上有相同之处)
2、泛型类型参数:在【泛型类型】或【方法定义】中,类型参数是客户端在实例化泛型类型的变量时指定的特定类型的占位符。就是那个list<T>中的T
类型参数的约束:在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。 如果客户端代码尝试使用某个约束所不允许的类型来实例化类 ,则会产生编译时错误。 这些限制称为约束。 约束是使用 where 上下文关键字指定的。 采用“基类、接口、构造器、值类型、引用类型”的约束方式来实现对类型能数 的“显式约束“, C#泛型要求对“所有泛型类型或泛型方法的类型参数”的任何假定,都要基于“显示的约束”,以维护C# 所要求的类型安全
16、请谈一下你对同步和异步,多线程的理解。请结合实际场景说明你在项目中的使用情况。
并发:同时处理多件事情,在处理第一个请求时同时响应第二个请求;
同步:同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行,同步 调用在继续之前等待响应或返回值。如果不允许调用继续,就说调用被阻塞 了
异步:并发的一种形式,(1)它采用回调机制,避免产生不必要的线程。(2)多线程也可以成为实现程序异步的一种方式,在这里 异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段(这违反了异步操作的本质)
多线程:并发的另一种形式,它采用多个线程来执行程序。对多个线程的管理使用了线程池。
并行处理:把正在执行的大量任务,分割成小块分配个多个运行的线程,线程池是存放任务的队列,这个队列能根据需要自行调整。由此产生了并行处理这个概念,多线程的一种,而多线程是并发的一种
异步优缺点:
因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些出入,而且难以调试。
多线程优缺点:
多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现
两者适用范围:
在了解了线程与异步操作各自的优缺点之后,我们可以来探讨一下线程和异步的合理用途。我认为:当需要执行I/O操作时,使用异步操作比使用线程+同步 I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.net Remoting等跨进程的调用。
而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处理大量的并发操作时就不合适了。
17 线程池和TPL
线程池能管理很多个线程。和手动创建一个线程来执行特定操作不同,我们将工作任务扔到线程池中,它会选择合适的线程,然后去执行我们给定的方法。线程池通过限制创建线程的总数,根据给定的工作量来决定创建合适的线程数量,降低了在极端情况下,如处理量比较少的情况下创建和销毁线程的开销,帮助我们解决了对系统资源的独占和过度使用。
TPL=>TASK Parallel Library 并行库,它是基于线程池的, 一种框架要使得并行执行不会产生太多的线程,能够保证工作量能够平均的分配到所有线程上,并且能够报告错误和产生可靠地结果,这就是并行库Task Parallel Library的工作,任务并行化是指通过一系列API将大的任务分解成一系列小的任务,然后再多线程上并行执行 (TPL)并行库有一系列API能够基于线程池同时管理成千上万个任务。TPL的核心是System.Threading.Tasks类,他代表一个小的任务,“Task是一种对线程和线程池里面的工作项的一种结构话抽象
18、请说明using和new的区别。
new有两种用法:1.实例化一个对象 2.声明隐藏方法
1、using导入其它命名空间中定义的类型,这样,您就不必在该命名空间中限定某个类型的使用
2、using为命名空间或类型创建别名 using Project = PC.MyCompany.Project; (2)为类去别名,右边不能有开放式泛型类型list<T>,必须会LIST<INT>这种
3、using提供一种能确保正确使用Idisposable对象的比较方便的语法;(idisposable接口有一个方法DIspose()我们一般用它释放对象占用的资源),它定义了一个范围,在此范围内的末尾将执行dispose方法(即使范围内发生异常也会执行dispose()方法)
19、 dynamic 动态类型:标记了dynamic标记的表达式在运行时才会被解析。编译时,不会被解析。可以用dynamic表达式调用 类型成员。它的出现方便了开发人员使用反射或者与其他非c#组建通信(例如com,例如html dom)。dynamic被编译后,实际是一个 object类型,只不过编译器会对dynamic类型进行特殊处理,让它在编译期间不进行任何的类型检查,而是将类型检查放到了运行期。
20、反射和序列化:
允许在运行时发现并使用编译时还不了解的类型及成员
1 由于反射严重依赖字符串,造成反射在编译时无法保证类型安全,例如Type.GetType("int")
2 反射速度慢,使用反射时类型及其成员的名称在编译时未知,你要用字符串名称标识每个类型及其成员,然后在运行时发现他们。反射机制会不停的在程序集的元数据中执行字符串搜索,字符串搜索执行的是不区分大小写的比较。从而影响速度。使用反射调用方法也会影响性能。尽量避免使用反射来访问字段或调用方法
反射:程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程序集、模块和类型的对象。您可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段和属性
序列化:序列化是将对象转换为容易传输的格式的过程。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象。在另一端,反序列化将从该流重新构造对象。
21、特性:
特性是为程序添加元数据的一种机制(存储在程序文件里的元数据),特性本身就是用来描述数据的元数据(metadata),通过它可以给编译器提供指示或者提供对数据的说明,特性是一个对象,它可以加载到程序集及程序集的对象中,这些对象包括 程序集本身、模块、类、接口、结构、构造函数、方法、方法参数等,加载了特性的对象称作特性的目标。
.net内置的常见特性有:ObsoleteAttribute,使用方式:[Obsolete("该方法已经过期")] 此时在你调用打了标记的方法时,会发出警告消息,来提醒你该方法已经过期
自定义特性:
1、自定义特性类与定义一个类没有什么区别,只需要继承自Attribute基类即可
2、特性类(metadata)本身需要三个特性去描述它Serializable、AttributeUsage 和 ComVisible,由于特性本身就是用来描述数据的元数据,所以描述元数据的特性被又称为元元数据meta-metadata,大多数情况下我们只需要掌握AttributeUsage 特性就好了,该特性有三个参数:
ValidOn:这是特性应用位置参数,定义了一个自定义特性能够应用到那种编程元素上,是方法还是属性
AllowMultiple:命名参数,它指定一个特性能否在一个程序集中被多次使用
Inherited:命名参数,用于控制一个自定义特性是否能被该类型的子类继承
其中,位置参数ValidOn使用AttributeTargets枚举
例如:[AttributeUsage(AttributeTargets.Method)] //控制自定义特性的使用,该特性就只能用于方法上面{可以不设置}
一般通过反射来遍历检查应用的特性信息,我们可以借助该信息来做权限,安全,登录状态验证
21 扩展方法:
如果要给已编译好的类添加新功能,开发人员一般只能从这个类中派生一个基类,或者更改这个类的源代码方式;
扩展方法的出现解决了这个问题,我们可以在一个单独的类中对已经存在的类进行扩展,为其添加扩展方法并不需要对源代码进行改动或继承基类
定义扩展方法:
* 1、扩展方法必须要定义到一个静态类中,扩展方法本身也必须是静态的
* 2、扩展方法首个参数必须是this 后面紧跟扩展的类 如:static void fun (this int i)
* 3、扩展方法可以被正确的对象实例调用 也可以使用静态类名进行静态调用
* 4、扩展方法的名称不能与要扩展的类名相同 编译时,扩展方法的优先级要低于扩展类本身方法的优先级
* 5、不能再扩展方法中直接访问扩展类的成员变量
22、 表达式树
表达式树提供一个将可执行代码转换成数据的方法,如果你要在执行代码之前修改或转换此代码他将变得很有价值。
表达式树被创建是为了制造一个像将查询表达式转换成字符串以传递给其他程序并在那里执行这样的转换任务。把代码,转换成数据,然后分析数据发现其组成部分,最后转换成可以传递到其他程序的字符串,Linq to SQL,实际上它就是C#作为源语言,SQL作为目标语言的一个编译过程, 一个LINQ to SQL查询不是在你的C#程序里执行的。它会被转换成SQL,通过网络发送,最后在数据库服务器上执行
表单式树有四个属性:
* Body:得到表达式的主体
* Parameters:得到lambda表达式的参数
* NodeType:获取树的节点的ExpressionType共45种不同值,包含所有表达式节点各种可能的类型,例如返回常量,例如返回参数,例如取两个值的小值(<),例如取两个值的大值(>),例如将值相加(+),等等。
23、在日常开发中你常用哪些设计模式?分别解决什么问题?请结合实际场景说明你在项目中的使用情况。
简单工厂、工厂方法、策略模式、抽象工厂模式、单利模式(将对象的构造函数设置为私有的,阻止外界直接实例化它,必须通过访问静态实例化属性/方法来创建)
24 CLR名词解释
CLR(common language runtime)公共语言运行时:clr 公共语言运行时,核心功能是内存管理,程序集加载,安全性,异常处理和线程同步。面向clr的所有语言都可以使用核心功能。在运行时clr不关心开发人员使用的是什么语言开发的源代码,因为针对每种语言都有一个面向clr的代码编译器,例如c#有针对c#的编译器,c++也有它自己的编译器。编译器将其最终生成托管模块。
托管模块: 托管模块是32位或者64位的windows可移植执行体pe32文件。需要clr才能运行。它包含四部分内容
程序集和托管模块:clr实际不和托管模块工作,它和程序集工作。程序集是一个抽象概念,他是一个或多个托管模块/资源文件的逻辑分组,程序集是重用和安全性以及版本控制的最小单元
托管代码和非托管代码:
在CLR监视下运行的程序属于“托管代码”,而不再CLR监视下直接在裸机上运行的程序属于“非托管代码”,非托管代码可以对系统进行低级控制,可以按照自己的想法管理内存,更方便的创建线程。c++编译器默认生成包含非托管代码的模块,并在运行时操作非托管内存数据。这些模块不需要clr就可运行,c++的编译器是独一无二的,它允许开发人员同时写托管代码和非托管代码,并生成到同一模块中。
FCL(framework class library) framework类库:
是一组DLL程序集的统称,是微软发布的数千个类型的定义,包含了很多辅助功能。开发人员可以利用这些程序集创建应用程序,例如:windows控制台,windows服务,web应用等
CTS (common type system) 通用类型系统:
CLR一切都围绕类型展开,类型向应用程序和其他类型公开功能,类型是clr的根本,所以微软制订了正式的规范来描述类型的定义和行为;例如cts规范规定了一个类型可以有0个或多个成员,成员包括字段,方法,属性,事件。cts他还规定了类型和成员的访问规则,例如标记为public还是product等。
CLS (common language specification)公共语言运行规范:
CLR集成了所有的语言,用一个语言创建的对象在另外一个语言中具有和它自己语言创建的对象同等的地位,但是各种语言在语法上存在很大差别,要创建其他语言都能访问的类型只能从自身语言床挑选其他语言可识别的功能;微软提供了一种语法规则,凡是符合cls公共语言允许规范的功能就能在其他语言中同样符合其规范的功能访问。它是cts/clr的一个子集。
实践题: