c# .net 面试总结

一. sql优化

1. 写明查询具体某几列,减少*的使用,表名过长时,尽量使用表的别名 *和列名一样

2,在业务密集的SQL当中尽量不采用IN操作符,用EXISTS 方案代替。

  in 和 exists的区别: 如果子查询得出的结果集记录较少,主查询中的表较大且又有索引时应该用in, 反之如果外层的主查询记录较少,子查询中的表大,又有索引时使用exists。其实我们区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询,所以我们会以驱动表的快速返回为目标,那么就会考虑到索引及结果集的关系了 ,另外IN时不对NULL进行处理。

  in 是把外表和内表作hash 连接,而exists是对外表作loop循环,每次loop循环再对内表进行查询。一直以来认为exists比in效率高的说法是不准确的。

3、模糊查询like,尽量少用%
  关键词%yue%,由于yue前面用到了“%”,因此该查询必然走全表扫描,除非必要,否则不要在关键词前加%4, 二者都能使用尽量使用where (与having比较)
  where 先过滤(数据就少了)再分组  
5,尽量使用多表连接(join)查询(避免子查询)

  子查询效率特别低,而一般的子查询都可以由关连查询来实现相同的功能,关联查询的效率要提高很多,所以建议在数据查询时避免使用子查询(尤其是在记录很多时),而最好用关联查询来实现。

6,建立索引

  较频繁地作为查询条件的字段,唯一性不太差的字段适合建立索引,更新不太频繁地字段适合创建索引,不会出现在where条件中的字段不该建立索引

7,多使用内部函数提高SQL效率

  例如多用concat连接,代替'||' 的符号连接

8,应尽量避免在 where 子句中使用 != 或 <> ,in 或 not in

  最好不要给数据库留NULL,尽可能的使用 NOT NULL填充数据库 (不然会进行全表扫描,影响效率) 

9,尽可能的使用 varchar/nvarchar 代替 char/nchar (节省字段存储空间)
View Code

二.接口和类有什么异同,抽象类和接口有什么区别

接口和类有什么异同
不同点:

1、接口不能直接实例化。

2、接口只包含方法或属性的声明,不包含方法的实现。

3、接口可以多继承,类只能单继承。

4、类有分部类的概念,定义可在不同的源文件之间进行拆分,而接口没有。(这个地方确实不对,接口也可以分部,谢谢@xclin163的指正)

5、表达的含义不同,接口主要定义一种规范,统一调用方法,也就是规范类,约束类,类是方法功能的实现和集合

相同点:

1、接口、类和结构都可以从多个接口继承。

2、接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员。

3、接口和类都可以包含事件、索引器、方法和属性。

抽象类和接口有什么区别
1、继承:接口支持多继承;抽象类不能实现多继承。

2、表达的概念:接口用于规范,更强调契约,抽象类用于共性,强调父子。抽象类是一类事物的高度聚合,那么对于继承抽象类的子类来说,对于抽象类来说,属于"Is A"的关系;而接口是定义行为规范,强调“Can Do”的关系,因此对于实现接口的子类来说,相对于接口来说,是"行为需要按照接口来完成"3、方法实现:对抽象类中的方法,即可以给出实现部分,也可以不给出;而接口的方法(抽象规则)都不能给出实现部分,接口中方法不能加修饰符。

4、子类重写:继承类对于两者所涉及方法的实现是不同的。继承类对于抽象类所定义的抽象方法,可以不用重写,也就是说,可以延用抽象类的方法;而对于接口类所定义的方法或者属性来说,在继承类中必须重写,给出相应的方法和属性实现。

5、新增方法的影响:在抽象类中,新增一个方法的话,继承类中可以不用作任何处理;而对于接口来说,则需要修改继承类,提供新定义的方法。

6、接口可以作用于值类型(枚举可以实现接口)和引用类型;抽象类只能作用于引用类型。

7、接口不能包含字段和已实现的方法,接口只包含方法、属性、索引器、事件的签名;抽象类可以定义字段、属性、包含有实现的方法。
View Code

三.C#中的委托是什么?事件是不是一种委托?

什么是委托?简单来说,委托类似于 C或 C++中的函数指针,允许将方法作为参数进行传递。

C#中的委托都继承自System.Delegate类型;
委托类型的声明与方法签名类似,有返回值和参数;
委托是一种可以封装命名(或匿名)方法的引用类型,把方法当做指针传递,但委托是面向对象、类型安全的;
事件可以理解为一种特殊的委托,事件内部是基于委托来实现的。
View Code

四.GC与内存管理

1. 简述一下一个引用对象的生命周期?
new创建对象并分配内存
对象初始化
对象操作、使用
资源清理(非托管资源)
GC垃圾回收
2. GC进行垃圾回收时的主要流程是?
① 标记:先假设所有对象都是垃圾,根据应用程序根Root遍历堆上的每一个引用对象,生成可达对象图,对于还在使用的对象(可达对象)进行标记(其实就是在对象同步索引块中开启一个标示位)。

② 清除:针对所有不可达对象进行清除操作,针对普通对象直接回收内存,而对于实现了终结器的对象(实现了析构函数的对象)需要单独回收处理。清除之后,内存就会变得不连续了,就是步骤3的工作了。

③ 压缩:把剩下的对象转移到一个连续的内存,因为这些对象地址变了,还需要把那些Root跟指针的地址修改为移动后的新地址。

6. GC在哪些情况下回进行回收工作?
内存不足溢出时(0代对象充满时)
Windwos报告内存不足时,CLR会强制执行垃圾回收
CLR卸载AppDomian,GC回收所有
调用GC.Collect
其他情况,如主机拒绝分配内存,物理内存不足,超出短期存活代的存段门限
7. using() 语法是如何确保对象资源被释放的?如果内部出现异常依然会释放资源吗?
using() 只是一种语法形式,其本质还是try…finally的结构,可以保证Dispose始终会被执行。

8. 解释一下C#里的析构函数?为什么有些编程建议里不推荐使用析构函数呢?
C#里的析构函数其实就是终结器Finalize,因为长得像C++里的析构函数而已。

有些编程建议里不推荐使用析构函数要原因在于:第一是Finalize本身性能并不好;其次很多人搞不清楚Finalize的原理,可能会滥用,导致内存泄露,因此就干脆别用了

9. Finalize() 和 Dispose() 之间的区别?
Finalize() 和 Dispose()都是.NET中提供释放非托管资源的方式,他们的主要区别在于执行者和执行时间不同:

finalize由垃圾回收器调用;dispose由对象调用。
finalize无需担心因为没有调用finalize而使非托管资源得不到释放,而dispose必须手动调用。
finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;而dispose一调用便释放非托管资源。
只有class类型才能重写finalize,而结构不能;类和结构都能实现IDispose。
另外一个重点区别就是终结器会导致对象复活一次,也就说会被GC回收两次才最终完成回收工作,这也是有些人不建议开发人员使用终结器的主要原因。

10. Dispose和Finalize方法在何时被调用?
Dispose一调用便释放非托管资源;
Finalize不能保证立即释放非托管资源,Finalizer被执行的时间是在对象不再被引用后的某个不确定的时间;
11. .NET中的托管堆中是否可能出现内存泄露的现象?
是的,可能会。比如:

不正确的使用静态字段,导致大量数据无法被GC释放;
没有正确执行Dispose(),非托管资源没有得到释放;
不正确的使用终结器Finalize(),导致无法正常释放资源;
其他不正确的引用,导致大量托管对象无法被GC释放;
12. 在托管堆上创建新对象有哪几种常见方式?
new一个对象;
字符串赋值,如string s1=”abc”;
值类型装箱;
View Code

五.多线程编程与线程同步

1. 描述线程与进程的区别?
一个应用程序实例是一个进程,一个进程内包含一个或多个线程,线程是进程的一部分;
进程之间是相互独立的,他们有各自的私有内存空间和资源,进程内的线程可以共享其所属进程的所有资源;
2. lock为什么要锁定一个参数,可不可锁定一个值类型?这个参数有什么要求?
lock的锁对象要求为一个引用类型。她可以锁定值类型,但值类型会被装箱,每次装箱后的对象都不一样,会导致锁定无效。

对于lock锁,锁定的这个对象参数才是关键,这个参数的同步索引块指针会指向一个真正的锁(同步块),这个锁(同步块)会被复用。

3. 多线程和异步有什么关系和区别?
多线程是实现异步的主要方式之一,异步并不等同于多线程。实现异步的方式还有很多,比如利用硬件的特性、使用进程或纤程等。在.NET中就有很多的异步编程支持,比如很多地方都有Begin***、End***的方法,就是一种异步编程支持,她内部有些是利用多线程,有些是利用硬件的特性来实现的异步编程。

4. 线程池的优点有哪些?又有哪些不足?
优点:减小线程创建和销毁的开销,可以复用线程;也从而减少了线程上下文切换的性能损失;在GC回收时,较少的线程更有利于GC的回收效率。

缺点:线程池无法对一个线程有更多的精确的控制,如了解其运行状态等;不能设置线程的优先级;加入到线程池的任务(方法)不能有返回值;对于需要长期运行的任务就不适合线程池。

5. Mutex和lock有何不同?一般用哪一个作为锁使用更好?
Mutex是一个基于内核模式的互斥锁,支持锁的递归调用,而Lock是一个混合锁,一般建议使用Lock更好,因为lock的性能更好。
View Code

 关于线程和并发相关的这边提供几篇不错的文章

  http://www.cnblogs.com/yunfeifei/p/3993401.html

  https://www.cnblogs.com/xinhuawei/p/5398193.html

六.重载与覆盖的区别

重载:当类包含两个名称相同但签名不同(方法名相同,参数列表不相同)的方法时发生方法重载。用方法重载来提供在语义上完成相同而功能不同的方法。

覆写:在类的继承中使用,通过覆写子类方法可以改变父类虚方法的实现。

主要区别:

1、方法的覆盖是子类和父类之间的关系,是垂直关系;方法的重载是同一个类中方法之间的关系,是水平关系。 
2、覆盖只能由一个方法,或只能由一对方法产生关系;方法的重载是多个方法之间的关系。 
3、覆盖要求参数列表相同;重载要求参数列表不同。 
4、覆盖关系中,调用那个方法体,是根据对象的类型来决定;重载关系,是根据调用时的实参表与形参表来选择方法体的。
View Code

七.virtual、sealed、override和abstract的区别

virtual申明虚方法的关键字,说明该方法可以被重写
sealed说明该类不可被继承
override重写基类的方法
abstract申明抽象类和抽象方法的关键字,抽象方法不提供实现,由子类实现,抽象类不可实例化。
View Code

八.装箱与拆箱

1.什么是拆箱和装箱?

装箱就是值类型转换为引用类型,拆箱就是引用类型(被装箱的对象)转换为值类型。

2.什么是箱子?

就是引用类型对象。

3.箱子放在哪里?

托管堆上。

4.装箱和拆箱有什么性能影响?

装箱和拆箱都涉及到内存的分配和对象的创建,有较大的性能影响。

5.如何避免隐身装箱?

编码中,多使用泛型、显示装箱。

6.箱子的基本结构?

上面说了,箱子就是一个引用类型对象,因此她的结构,主要包含两部分:
值类型字段值;
引用类型的标准配置,引用对象的额外空间:TypeHandle和同步索引块,关于这两个概念在本系列后面的文章会深入探讨。

7.装箱的过程?

1.在堆中申请内存,内存大小为值类型的大小,再加上额外固定空间(引用类型的标配:TypeHandle和同步索引块);
2.将值类型的字段值(x=1023)拷贝新分配的内存中;
3.返回新引用对象的地址(给引用变量object o)

8.拆箱的过程?

1.检查实例对象(object o)是否有效,如是否为null,其装箱的类型与拆箱的类型(int)是否一致,如检测不合法,抛出异常;
2.指针返回,就是获取装箱对象(object o)中值类型字段值的地址;
3.字段拷贝,把装箱对象(object o)中值类型字段值拷贝到栈上,意思就是创建一个新的值类型变量来存储拆箱后的值;
View Code

九.什么是反射

程序集包含模块,而模块又包括类型,类型下有成员,反射就是管理程序集,模块,类型的对象,它能够动态的创建类型的实例,设置现有对象的类型或者获取现有对象的类型,能调用类型的方法和访问类型的字段属性。它是在运行时创建和使用类型实例;
事例

   public class ReflectionTest {
        
        /// <summary>
        /// 反射名称
        /// </summary>
        public string ReflectionName { get; set; }

        public string GetName()
        {
            return "张三";
        }
    }


   Type type = typeof(ReflectionTest);
            string name = type.Name;//获取当前成员的名称
            string fullName = type.FullName;//获取类的全部名称不包括程序集
            string nameSpace = type.Namespace;//获取该类的命名空间
            var assembly = type.Assembly;//获取该类的程序集名
            var module = type.Module;//获取该类型的模块名            
            var memberInfos = type.GetMembers();//得到所有公共成员
   
 //获取当前执行代码的程序集
            Assembly assem = Assembly.GetExecutingAssembly();
            Console.WriteLine(assem.FullName);

            var types = assem.GetTypes();//程序集下所有的类
            Console.WriteLine("程序集包含的类型:");
            foreach (var item in types) {
                Console.WriteLine("" + item.Name);
            }
View Code

十.数据库常见的操作--事务,存储过程,游标,触发器等

1.维护数据库的完整性、一致性、你喜欢用触发器还是自写业务逻辑?为什么?

答:尽可能用约束(包括CHECK、主键、唯一键、外键、非空字段)实现,这种方式的效率最好;其次用触发器,这种方式可以保证无论何种业务系统访问数据库都能维持数据库的完整性、一致性;最后再考虑用自写业务逻辑实现,但这种方式效率最低、编程最复杂,当为下下之策。

2.什么是事务?什么是锁?

答:事务是指一个工作单元,它包含了一组数据操作命令,并且所有的命令作为一个整体一起向系统提交或撤消请求操作,即这组命令要么都执行,要么都不执行。

  锁是在多用户环境中对数据的访问的限制。SqlServer自动锁定特定记录、字段或文件,防止用户访问,以维护数据安全或防止并发数据操作问题,锁可以保证事务的完整性和并发性。

 3.什么是索引,有什么优点?

答:索引象书的目录类似,索引使数据库程序无需扫描整个表,就可以在其中找到所需要的数据,索引包含了一个表中包含值的列表,其中包含了各个值的行所存储的位置,索引可以是单个或一组列,索引提供的表中数据的逻辑位置,合理划分索引能够大大提高数据库性能。

 4.视图是什么?游标是什么?

答:视图是一种虚拟表,虚拟表具有和物理表相同的功能,可以对虚拟表进行增该查操作;

    视图通常是一个或多个表的行或列的子集;

   视图的结果更容易理解(修改视图对基表不影响),获取数据更容易(相比多表查询更方便),限制数据检索(比如需要隐藏某些行或列),维护更方便。

   游标对查询出来的结果集作为一个单元来有效的处理,游标可以定位在结果集的特定行、从结果集的当前位置检索一行或多行、可以对结果集中当前位置进行修改、

 5.什么是存储过程?有什么优点?

答:存储过程是一组予编译的SQL语句

    它的优点:1.允许模块化程序设计,就是说只需要创建一次过程,以后在程序中就可以调用该过程任意次。

              2.允许更快执行,如果某操作需要执行大量SQL语句或重复执行,存储过程比SQL语句执行的要快。

               3.减少网络流量,例如一个需要数百行的SQL代码的操作有一条执行语句完成,不需要在网络中发送数百行代码。

        4.更好的安全机制,对于没有权限执行存储过程的用户,也可授权他们执行存储过程。

 6.什么是触发器?

答:触发器是一种特殊类型的存储过程,出发器主要通过事件触发而被执行的,

  触发器的优点:1.强化约束,触发器能够提供比CHECK约束;

         2.跟踪变化,触发器可以跟踪数据库内的操作,从而不允许未经允许许可的更新和变化;

         3.联级运算,比如某个表上的触发器中包含对另一个表的数据操作,而该操作又导致该表上的触发器被触发
View Code

 

后续还会慢慢补充,自己碰到或者看到比较好的一些知识都会一一记录下来  如果有好的知识点 大家记得分享哦

posted @ 2019-03-29 14:05  陌陌逸帆  阅读(2057)  评论(6编辑  收藏  举报