.NET面试题5
常见面试题目:
1. const和readonly有什么区别?
2. 哪些类型可以定义为常量?常量const有什么风险?
3. 字段与属性有什么异同?
4. 静态成员和非静态成员的区别?
5. 自动属性有什么风险?
6. 特性是什么?如何使用?
7. 下面的代码输出什么结果?为什么?
List<Action> acs = new List<Action>(5);
for (int i = 0; i < 5; i++)
{
acs.Add(() => { Console.WriteLine(i); });
}
acs.ForEach(ac => ac());
8. C#中的委托是什么?事件是不是一种委托?
字段与属性的恩怨
常量
常量的基本概念就不细说了,关于常量的几个特点总结一下:
- 常量的值必须在编译时确定,简单说就是在定义是设置值,以后都不会被改变了,她是编译常量。
- 常量只能用于简单的类型,因为常量值是要被编译然后保存到程序集的元数据中,只支持基元类型,如int、char、string、bool、double等。
- 常量在使用时,是把常量的值内联到IL代码中的,常量类似一个占位符,在编译时被替换掉了。正是这个特点导致常量的一个风险,就是不支持跨程序集版本更新;
关于常量不支持跨程序集版本更新,举个简单的例子来说明:
public class A
{
public const int PORT = 10086; public virtual void Print()
{
Console.WriteLine(A.PORT);
}
}
上面一段非常简单代码,其生产的IL代码如下,在使用常量变量的地方,把她的值拷过来了(把常量的值内联到使用的地方),与常量变量A.PORT没有关系了。假如A引用了B程序集(B.dll文件)中的一个常量,如果后面单独修改B程序集中的常量值,只是重新编译了B,而没有编译程序集A,就会出问题了,就是上面所说的不支持跨程序集版本更新。常量值更新后,所有使用该常量的代码都必须重新编译,这是我们在使用常量时必须要注意的一个问题。
- 不要随意使用常量,特别是有可能变化的数据;
- 不要随便修改已定义好的常量值;
补充一下枚举的本质
接着上面的const说,其实枚举enum也有类似的问题,其根源和const一样,看看代码你就明白了。下面的是一个简单的枚举定义,她的IL代码定义和const定义是一样一样的啊!枚举的成员定义和常量定义一样,因此枚举其实本质上就相当是一个常量集合。
public enum EnumType : int
{
None=0,
Int=1,
String=2,
}
关于字段
字段本身没什么好说的,这里说一个字段的内联初始化问题吧,可能容易被忽视的一个小问题(不过好像也没什么影响),先看看一个简单的例子:
public class SomeType
{
private int Age = 0;
private DateTime StartTime = DateTime.Now;
private string Name = "三体";
}
定义字段并初始化值,是一种很常见的代码编写习惯。但注意了,看看IL代码结构,一行代码(定义字段+赋值)被拆成了两块,最终的赋值都在构造函数里执行的。
那么问题来了,如果有多个构造函数,就像下面这样,有多半个构造函数,会造成在两个构造函数.ctor中重复产生对字段赋值的IL代码,这就造成了不必要的代码膨胀。这个其实也很好解决,在非默认构造函数后加一个“:this()”就OK了,或者显示的在构造函数里初始化字段。
public class SomeType
{
private DateTime StartTime = DateTime.Now; public SomeType() { } public SomeType(string name)
{
}
}
属性的本质
属性是面向对象编程的基本概念,提供了对私有字段的访问封装,在C#中以get和set访问器方法实现对可读可写属性的操作,提供了安全和灵活的数据访问封装。我们看看属性的本质,主要手段还是IL代码:
public class SomeType
{
public int Index { get; set; } public SomeType() { }
}
上面定义的属性Index被分成了三个部分:
- 自动生成的私有字段“<Index>k__BackingField”
- 方法:get_Index(),获取字段值;
- 方法:set_Index(int32 'value'),设置字段值;
因此可以说属性的本质还是方法,使用面向对象的思想把字段封装了一下。在定义属性时,我们可以自定义一个私有字段,也可以使用自动属性“{ get; set; } ”的简化语法形式。
使用自动属性时需要注意一点的是,私有字段是由编译器自动命名的,是不受开发人员控制的。正因为这个问题,曾经在项目开发中遇到一个因此而产生的Bug:
这个Bug是关于序列化的,有一个类,定义很多个(自动)属性,这个类的信息需要持久化到本地文件,当时使用了.NET自带的二进制序列化组件。后来因为一个需求变更,把其中一个字段修改了一下,需要把自动属性改为自己命名的私有字段的属性,就像下面实例这样。测试序列化到本地没有问题,反序列化也没问题,但最终bug还是被测试出来了,问题在与反序列化以前(修改代码之前)的本地文件时,Index属性的值丢失了!!!
private int _Index;
public int Index
{
get { return _Index; }
set { _Index = value; }
}
因为属性的本质是方法+字段,真正的值是存储在字段上的,字段的名称变了,反序列化以前的文件时找不到对应字段了,导致值的丢失!这也就是使用自动属性可能存在的风险。
委托与事件
什么是委托?简单来说,委托类似于 C或 C++中的函数指针,允许将方法作为参数进行传递。
- C#中的委托都继承自System.Delegate类型;
- 委托类型的声明与方法签名类似,有返回值和参数;
- 委托是一种可以封装命名(或匿名)方法的引用类型,把方法当做指针传递,但委托是面向对象、类型安全的;
委托的本质——是一个类
.NET中没有函数指针,方法也不可能传递,委托之所可以像一个普通引用类型一样传递,那是因为她本质上就是一个类。下面代码是一个非常简单的自定义委托:
public delegate void ShowMessageHandler(string mes);
看看她生产的IL代码
我们一行定义一个委托的代码,编译器自动生成了一堆代码:
- 编译器自动帮我们创建了一个类ShowMessageHandler,继承自System.MulticastDelegate(她又继承自System.Delegate),这是一个多播委托;
- 委托类ShowMessageHandler中包含几个方法,其中最重要的就是Invoke方法,签名和定义的方法签名一致;
- 其他两个版本BeginInvoke和EndInvoke是异步执行版本;
因此,也就不难猜测,当我们调用委托的时候,其实就是调用委托对象的Invoke方法,可以验证一下,下面的调用代码会被编译为对委托对象的Invoke方法调用:
private ShowMessageHandler ShowMessage; //调用
this.ShowMessage("123");
.NET的闭包
闭包提供了一种类似脚本语言函数式编程的便捷、可以共享数据,但也存在一些隐患。
题目列表中的第7题,就是一个.NET的闭包的问题。
List<Action> acs = new List<Action>(5);
for (int i = 0; i < 5; i++)
{
acs.Add(() => { Console.WriteLine(i); });
}
acs.ForEach(ac => ac()); // 输出了 5 5 5 5 5,全是5?这一定不是你想要的吧!这是为什么呢?
上面的代码中的Action就是.NET为我们定义好的一个无参数无返回值的委托,从上一节我们知道委托实质是一个类,理解这一点是解决本题的关键。在这个地方委托方法共享使用了一个局部变量i,那生成的类会是什么样的呢?看看IL代码:
共享的局部变量被提升为委托类的一个字段了:
- 变量i的生命周期延长了;
- for循环结束后字段i的值是5了;
- 后面再次调用委托方法,肯定就是输出5了;
那该如何修正呢?很简单,委托方法使用一个临时局部变量就OK了,不共享数据:
List<Action> acss = new List<Action>(5);
for (int i = 0; i < 5; i++)
{
int m = i;
acss.Add(() => { Console.WriteLine(m); });
}
acss.ForEach(ac => ac()); // 输出了 0 1 2 3 4
至于原理,可以自己探索了!
题目答案解析:
1. const和readonly有什么区别?
const关键字用来声明编译时常量,readonly用来声明运行时常量。都可以标识一个常量,主要有以下区别:
1、初始化位置不同。const必须在声明的同时赋值;readonly即可以在声明处赋值,也可以在构造方法里赋值。
2、修饰对象不同。const即可以修饰类的字段,也可以修饰局部变量;readonly只能修饰类的字段 。
3、const是编译时常量,在编译时确定该值,且值在编译时被内联到代码中;readonly是运行时常量,在运行时确定该值。
4、const默认是静态的;而readonly如果设置成静态需要显示声明 。
5、支持的类型时不同,const只能修饰基元类型或值为null的其他引用类型;readonly可以是任何类型。
2. 哪些类型可以定义为常量?常量const有什么风险?
基元类型或值为null的其他引用类型,常量的风险就是不支持跨程序集版本更新,常量值更新后,所有使用该常量的代码都必须重新编译。
3. 字段与属性有什么异同?
- 属性提供了更为强大的,灵活的功能来操作字段
- 出于面向对象的封装性,字段一般不设计为Public
- 属性允许在set和get中编写代码
- 属性允许控制set和get的可访问性,从而提供只读或者可读写的功能 (逻辑上只写是没有意义的)
- 属性可以使用override 和 new
4. 静态成员和非静态成员的区别?
- 静态变量使用 static 修饰符进行声明,静态成员在加类的时候就被加载(上一篇中提到过,静态字段是随类型对象存放在Load Heap上的),通过类进行访问。
- 不带有static 修饰符声明的变量称做非静态变量,在对象被实例化时创建,通过对象进行访问 。
- 一个类的所有实例的同一静态变量都是同一个值,同一个类的不同实例的同一非静态变量可以是不同的值 。
- 静态函数的实现里不能使用非静态成员,如非静态变量、非静态函数等。
5. 自动属性有什么风险?
因为自动属性的私有字段是由编译器命名的,后期不宜随意修改,比如在序列化中会导致字段值丢失。
6. 特性是什么?如何使用?
特性与属性是完全不相同的两个概念,只是在名称上比较相近。Attribute特性就是关联了一个目标对象的一段配置信息,本质上是一个类,其为目标元素提供关联附加信息,这段附加信息存储在dll内的元数据,它本身没什么意义。运行期以反射的方式来获取附加信息。使用方法可以参考:http://www.cnblogs.com/anding/p/5129178.html
7. 下面的代码输出什么结果?为什么?
List<Action> acs = new List<Action>(5);
for (int i = 0; i < 5; i++)
{
acs.Add(() => { Console.WriteLine(i); });
}
acs.ForEach(ac => ac());
输出了 5 5 5 5 5,全是5!因为闭包中的共享变量i会被提升为委托对象的公共字段,生命周期延长了
8. C#中的委托是什么?事件是不是一种委托?
什么是委托?简单来说,委托类似于 C或 C++中的函数指针,允许将方法作为参数进行传递。
- C#中的委托都继承自System.Delegate类型;
- 委托类型的声明与方法签名类似,有返回值和参数;
- 委托是一种可以封装命名(或匿名)方法的引用类型,把方法当做指针传递,但委托是面向对象、类型安全的;
事件可以理解为一种特殊的委托,事件内部是基于委托来实现的。
版权所有,文章来源:http://www.cnblogs.com/anding
个人能力有限,本文内容仅供学习、探讨,欢迎指正、交流。
.NET面试题5的更多相关文章
- .NET面试题系列[8] - 泛型
“可变性是以一种类型安全的方式,将一个对象作为另一个对象来使用.“ - Jon Skeet .NET面试题系列目录 .NET面试题系列[1] - .NET框架基础知识(1) .NET面试题系列[2] ...
- 关于面试题 Array.indexof() 方法的实现及思考
这是我在面试大公司时碰到的一个笔试题,当时自己云里雾里的胡写了一番,回头也曾思考过,最终没实现也就不了了之了. 昨天看到有网友说面试中也碰到过这个问题,我就重新思考了这个问题的实现方法. 对于想进大公 ...
- 对Thoughtworks的有趣笔试题实践
记得2014年在网上看到Thoughtworks的一道笔试题,当时觉得挺有意思,但是没动手去写.这几天又在网上看到了,于是我抽了一点时间写了下,我把程序运行的结果跟网上的答案对了一下,应该是对的,但是 ...
- 从阿里巴巴笔试题看Java加载顺序
一.阿里巴巴笔试题: public class T implements Cloneable { public static int k = 0; public static T t1 = new T ...
- JAVA面试题
在这里我将收录我面试过程中遇到的一些好玩的面试题目 第一个面试题:ABC问题,有三个线程,工作的内容分别是打印出"A""B""C",需要做的 ...
- C++常考面试题汇总
c++面试题 一 用简洁的语言描述 c++ 在 c 语言的基础上开发的一种面向对象编程的语言: 应用广泛: 支持多种编程范式,面向对象编程,泛型编程,和过程化编程:广泛应用于系统开发,引擎开发:支持类 ...
- .NET面试题系列[4] - C# 基础知识(2)
2 类型转换 面试出现频率:主要考察装箱和拆箱.对于有笔试题的场合也可能会考一些基本的类型转换是否合法. 重要程度:10/10 CLR最重要的特性之一就是类型安全性.在运行时,CLR总是知道一个对象是 ...
- 我们公司的ASP.NET 笔试题,你觉得难度如何
本套试题共8个题,主要考察C#面向对象基础,SQL和ASP.NET MVC基础知识. 第1-3题会使用到一个枚举类,其定义如下: public enum QuestionType { Text = , ...
- 我设计的ASP.NET笔试题,你会多少呢
本笔试题考查范围包括面向对象基础.HTML.CSS.JS.EF.jQuery.SQL.编码思想.算法等范围. 第1题:接口和抽象类有何区别? 第2题:静态方法和实例方法有何区别? 第3题:什么是多态? ...
- 猫哥网络编程系列:详解 BAT 面试题
从产品上线前的接口开发和调试,到上线后的 bug 定位.性能优化,网络编程知识贯穿着一个互联网产品的整个生命周期.不论你是前后端的开发岗位,还是 SQA.运维等其他技术岗位,掌握网络编程知识均是岗位的 ...
随机推荐
- Oauth Client Credentials Grant
http://www.cnblogs.com/dudu/p/4569857.html OAuth真是一个复杂的东东,即使你把OAuth规范倒背如流,在具体实现时也会无从下手.因此,Microsoft. ...
- Javascript之入门篇(一)
上一篇学习了什么是JavaScript语言及其作用和特有的特点等,本篇将详细介绍JavaScript一些入门使用方式. 对于初学者来讲,由于JavaScript是嵌入到HTML页面里面的,首先创建一张 ...
- loj #2006. 「SCOI2015」小凸玩矩阵
#2006. 「SCOI2015」小凸玩矩阵 题目描述 小凸和小方是好朋友,小方给小凸一个 N×M N \times MN×M(N≤M N \leq MN≤M)的矩阵 A AA,要求小凸从其中选出 ...
- vue_cli下开发一个简单的模块权限系统之展现数据
这个页面是用户列表:userList就是第二张截图中的data里面的userList vue中只要改变存放数据的载体就会实现页面改变,mounted的意思是页面加载时执行这里面的函数,我们需要在页面加 ...
- CSS之引入样式
CSS引入样式 内部样式 内嵌式是将CSS代码集中写在HTML文档的head头部标签中,并且用style标签定义,其基本语法格式如下: <head> <style type=&quo ...
- 小程序渲染问题:ios显示安卓不显示
问题描述: 测试库转到正式库后添加数据,小程序数据渲染不出来,但是测试库没问题,ios数据能显示,没问题,但是安卓显示没数据. 排除是服务器https证书问题,如果是证书问题,小程序会直接调不了接口. ...
- python安装出现的证书问题
1. pip install pyenv 安装时出现下图错误 Could not install packages due to an EnvironmentError: HTTPSConnectio ...
- csv HTTP简单表服务器
HTTP Simple Table Server Download Performance testing with JMeter can be done with several JMeter in ...
- poj2513连接木棍(字典树+欧拉回路+并查集)
题目传送门 题目大意:给你一堆木棍,每根木管都有两种颜色,相同颜色的部分可以连接起来,问你这堆木棍可不可以连接成1根. 思路:大致的思路很好想,就是判断欧拉回路的方法(1.联通,2,要么顶点读书全为偶 ...
- redis备份恢复
redis的几种数据导入导出方式[转] 环境说明:202.102.221.11 redis源实例202.102.221.12 redis目标实例202.102.221.13 任意linux系统 一 ...