2017年,发布了C#7.0。公司进行了一次考试,本人在考试结束后回顾试卷,经过查阅和汇总,写了一份答题解析,并分享给同事们,原为word文档。在整理磁盘时找到了该文件,现重新整理如下。大部分内容甚至因为基本用不到已经遗忘,也算是重新回顾一下吧。
解析:
属性,像数据成员一样被访问,像方法(函数)一样被实现。
C#将属性从一些特殊习惯提升为第一等的语言特性。是对访问或者修改内部数据的方法的扩展。
C#2.0:常规属性,可以对get/set指定不同级别的访问修饰符,使你对数据成员的可见性有更好的控制。
C#3.0:出现了自动实现的属性(Auto-Implemented Properties)。
(1) 依旧需要使用访问修饰符给get访问器或者set访问器以控制访问权限。
(2) 必须同时实现set访问器和get访问器,缺一不可。
(3) 自动实现的属性,编译器在运行时会自动生成一个私有的字段,这个自动生成的字段不能直接访问。
(4) 当需要实现对数据的合法性验证或者其它特殊处理的时候不能用自动实现的属性。
(5) 要初始化就必须要在构造函式中去完成初始值的设定。
C#6.0:自动实现属性的优化。
可以用如下写法: public int ReadOnlyAge {get;} = 30; // 唯读的也可以初始设定
Readonly:是可在字段声明上使用的修饰符。与const关键字类似使用,但是const为编译时常量,readonly为运行时常量。编译时常量性能较好,运行时常量灵活性较好。
解析:
VAR是3.0出的一个弱化类型(隐式类型)的定义。其类似object但是效率比object高点,因为在运行前就会被编译器推断出其类型。主要用于在声明变量时,无法确定数据类型使用的。可理解为声明变量的占位符。
用法总结:
(1)必须在定义时初始化。也就是必须是var s=”asd”,而不能是:var s;s=”asd”。因为编译器会根据初始值来推断出具体的类型。
(2)无法将null赋值给隐式(var)类型化的局部变量。
(3) 初始化不能是一个匿名方法(匿名委托、匿名函数)。
var t = delegate(int i) { return i; };
//报错:无法将"匿名方法"赋予隐式类型的局部变量
(4)可以用匿名类初始化,但是属性一定要赋初始值。
var person = new { Age = 18, Name = "Kobe" };
//正确
var person = new { Age, Name };
//报错:当前上下文中不存在"Age" 当前上下文中不存在"Name"
(5)一旦初始化完成,就不能再给变量赋予与初始化值类型不同的值了。
(6)Var声明的必须是局部变量(不包括类级别的变量)。Var定义必须是在方法中,或者属性get、set访问器中。
(7)使用var定义变量和object不同,它在效率使用上和强类型方式定义变量一样。
(8)Var不能用作方法的参数。
public void Test(var t){}
//报错:上下文关键字"var"只能出现在局部变量声明中
(9)Var不能当作返回值类型。
public var Test(){}
//报错:并非所有代码路径都返回值 或者:上下文关键字"var"只能出现在局部变量声明中
解析:
Lambda表达式:一种可用于创建委托或表达式目录树类型的匿名函数。
C#2.0:引入了匿名方法。
C#3.0:引入了Lambda表达式,取代了匿名方法,作为编写内联代码的首选方式。
Lambda表达式往往是学习Linq的基础。
System.Linq:
SELECT:
//将序列中的每个元素投影到新表中。返回元素为对source 的每个元素调用转换函数的结果。
IEnumerable Select(this IEnumerable source, Func selector);
WHERE:
//基于谓词筛选值序列。返回包含输入序列中满足条件的元素。
IEnumerable Where(this IEnumerable source, Func predicate);
ALL:
//确定序列中的所有元素是否满足条件。如果源序列中的每个元素都通过指定谓词中的测试,或者序列为空,则为 true;否则为 false。
Bool All(this IEnumerable source, Func predicate);
ANY:
//确定序列中的任何元素是否都满足条件。如果源序列中的任何元素都通过指定谓词中的测试,则为 true;否则为 false。
bool Any(this IEnumerable source, Func predicate);
Func:
Func是一个委托。封装一个具有零个或多个指定类型的参数并返回一个指定类型的结果值的方法。
解析:
Linq:语言集成查询。Language Integrated Queryd 的简称。C# 3.0 中加入的新特性。
程序集 | 命名空间 | 描述 | |
---|---|---|---|
LINQ to Objects | System.Core.dll | System.Linq | 提供对内存中集合操作的支持 |
LINQ to XML | System.Xml.Linq.dll | System.Xml.Linq | 提供对XML数据源的操作的支持 |
LINQ to SQL | System.Data.Linq.dll | System.Data.Linq | 提供对Sql Server数据源的操作的支持。(微软宣布已不再更新,推荐使用LINQ to Entities) |
LINQ to DataSet | System.Data.DataSetExtensions.dll | System.Data | 提供对离线数据操作的支持 |
LINQ to Entities | System.Core.dll; System.Data.Entity.dll | System.Linq;System.Data.Objects | Linq to Entities是Entity Framework的一部分并且取代Linq to SQL作为在数据库上使用Linq的标准机制。(Entity Framework是由微软发布的开源对象-关系映射(ORM)框架,支持多种数据库) |
LINQ查询表达式:
关键字 | 功能 |
---|---|
约束 | Linq查询表达式必须以from子句开头,以select或者group子句结束 |
from…in… | 指定要查找的数据源以及范围变量,多个from子句则表示从多个数据源查找数据。注意:C#编译器会把“复合from子句“的查询表达式转换为SelectMany()扩展方法。 |
join…in…on…equals… | 指定多个数据源的关联方式 |
Let | 引入用于存储查询表达式中子表达式结果的范围变量 |
Orderby、desceding | 指定元素的排序字段和排序方式。当有多个排序字段时,由字段顺序确定主次关系,可指定升序和降序。 |
where | 指定元素的筛选条件。多个where子句则表示了并列条件,必须全部都满足了才能入选。每个where子句可用谓词&&、 |
group | 指定元素的分组字段。 |
select | 制定查询要返回的目标数据,可指定任何类型,甚至是匿名类型。 |
into | 提供一个临时的标识符。该标识可以引用join、group和select子句的结果。(1)直接出现在join子句之后的into关键字会被翻译为GroupJoin。(into之前的查询变量可以继续使用)(2)Select或group子句之后的into关键字它会重新开始一个查询,我们可以继续引入where、orderby、select子句,它是对分步构建查询表达式的一种简写方式。(into之前的查询变量都不可再使用) |
解析:
Dynamic是C#4.0的新特性,一个静态类型,但是在编译时会跳过静态类型的检查。 在本质上它与Var类型是有区别的。
通过IL代码验证可以知道dynamic变量是一个object变量。Long类型就是int64。
解析:
C#4.0增加新特性,元组Tuple,它是一种固定成员的泛型集合。
.NET Framework 直接支持具有 1 到 7 元素的元组。 此外,可以创建由嵌套中的元组对象的元组的八个或多个元素Rest属性Tuple
前7个元素写法为:Tuple.Item1, Tuple.Item2…Tuple.Item7。
然而第8个及以后的元素调用写法为:Tuple.Rest.Item1, Tuple.Rest.Item2…
不足:
(1)访问元素的时候只能通过ItemX去访问,使用前需要明确元素顺序,属性名字没有实际意义,不方便记忆;
(2)最多有八个元素,要想更多只能通过最后一个元素进行嵌套扩展;
(3)Tuple是一个引用类型,不像其它的简单类型一样是值类型,它在堆上分配空间,在CPU密集操作时可能有太多的创建和分配工作。
C# 7.0 增加新特性,值元组ValueTuple,.Net Framework 4.7以上版本可用。
值元组也是一种数据结构,用于表示特定数量和元素序列,但是和元组类的主要区别为:
(1)值元组是结构,是值类型,不是类,而元组(Tuple)是类,引用类型;
(2)值元组元素是可变的,不是只读的,也就是说可以改变值元组中的元素值;
(3)值元组的数据成员是字段不是属性。
优化区别:
(1)当构造出超过7个元素以上的值元组后,可以使用接下来的ItemX进行访问嵌套元组中的值,对于上面的例子,要访问第十个元素,既可以通过testTuple10.Rest.Item3访问,也可以通过testTuple10.Item10来访问:
var testTuple10 = new ValueTuple>(1, 2, 3, 4, 5, 6, 7, new ValueTuple(8, 9, 10));
Console.WriteLine($"Item 10: {testTuple10.Rest.Item3}, Item 10:{testTuple10.Item10}");
(2)返回值可以不明显指定ValueTuple,使用新语法(,)代替,如(string, int, uint):
static (string, int, uint) GetStudentInfo1(string name)
{
return ("Bob", 28, 175);
}
static void RunTest1()
{
var studentInfo = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
}
(3)返回值可以指定元素名字,方便理解记忆赋值和访问:
static(string name, int age, uint height) GetStudentInfo1(string name)
{
return ("Bob", 28, 175);
}
static void RunTest1()
{
var studentInfo = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Name [{studentInfo.name}],
Age [{studentInfo.age}], Height [{studentInfo.height}]");
}
C#7 ValueTuple 继续深入了解可参考此处
解析:
C#6.0新功能:Static Using Statements。
注意:只会引入指定类的扩展方法 (Extension Method),而不是引入某命名空间下的全部扩展方法。
A选项:没毛病。
B选项:Console是system空间下的,必须要引用system才行。
//报错:当前上下文中不存在Console。
C选项:WriteLine是System.Console下的,都已经Static了,没毛病。
D选项:都引用了,怎么写都没毛病。
解析:
C#6.0 新功能:Nameof Expressions 故名思义就是取得名字的表达式。
用于获取变量、类型或成员的简单(非限定)字符串名称。
解析:
C#7.0:C#7.0 中引入了引用返回(ref return)的概念,允许C#方法中返回一个值类型的引用。
这样,我们通过C#7.0,能直接将调用栈(call stack)上的引用返回。
并且,对于体积较大的结构体(struct),返回引用比传递结构值要快很多,因为结构体的赋值会对整个结构进行拷贝。
另外需要注意的是,ref return的引用,在语言层面附加规则,不允许返回方法内的局部变量的引用,换句话说,被返回的堆栈地址,必须低于当前方法的入口地址。
感觉比较绕,这个7.0的优化貌似受众面不广。
解析:
C#7.0:允许_出现,作为数字分隔符。
Var a = 123_466;
Var x = 0*AB_CD_F;
可以将 _ 放入任意的数字之间,以提高可读性,它们对值没有影响。
此外,C#7.0 引入了二进制文字,这样你就可以指定二进制模式而不用去了解十六进制。
Var b = 0b1010_1011_1100_1101_1110_1111;
解析:
C#3.0:初始化列表,在对象初始化时顺便将成员属性初始化。
其中,若类的构造函数用的是默认构造函数,即没有参数的构造函数,初始化列表前的小括号“()“是可以去掉的。
分号一般用于语句结束完成或函数调用声明和定义自变量结束。
解析:
C#4.0:迎合多核处理的发展。它引入了一个新概念—任务(Task),作为支持并行运算的重要组成部分,同时也作为对线程池的一个补充和完善。
(1)使用构造函数创建Task:
Task t1 = new Task(MyMethod);
t1.Start();
(2)使用Task.Factory.StartNew进行创建Task:
Task t1 = Task.Factory.StartNew(MyMethod);
总结:任务给了我们更多的方便性、灵活性的同时,也带来了比线程池更多的资源消耗。如果想减少资源消耗,请直接使用线程池QueueUserWorkItem方法效果会更好;如果想要更多的控制与灵活性,任务(Task)是不二的选择。
解析:
同上单选(1)。
目前就两种方式可以赋值,1是构造方法2是属性初始化时。
解析:
同上多选(13),单选(1)。
解析:
Lambda表达式:同上单选(3)。
Lambda表达式的格式: (参数列表)=>表达式或语句块
Lambda表达式使用的示例(以下均为合法格式,注意小括号()和花括号{}的是否必须)
x, y) => x * y | 多参数,隐式类型=> 表达式 |
x => x * 5 | 单参数, 隐式类型=>表达式 |
x => { return x * 5; } | 单参数,隐式类型=>语句块 |
(int x) => x * 5 | 单参数,显式类型=>表达式 |
(int x) => { return x * 5; } | 单参数,显式类型=>语句块 |
() => Console.WriteLine | 无参数 |
解析:
C#6.0:优化索引初始化器(Index Initializers)。
解析:
C#6.0中新增了与异常相关的特性。使得我们可以在catch和finally中等待异步方法。
Await:运算符在异步方法应用于任务,以挂起执行方法,直到所等待的任务完成。
解析:
C#6.0:异常筛选器(Exception)。VB和F#中早就有的功能。
可以使用同一 try-catch 语句中的多个特定 catch 子句。 在这种情况下,catch 子句的顺序很重要,因为 catch 子句是按顺序检查的。 在使用更笼统的子句之前获取特定性更强的异常。 如果捕获块的排序使得永不会达到之后的块,则编译器将产生错误。
筛选想要处理的异常的一种方式是使用 catch 参数。 也可以使用谓词表达式进一步检查该异常以决定是否要对其进行处理。 如果谓词表达式返回 false,则继续搜索处理程序。
疑惑(待解决): 搜索出现了catch ()if()这样的用法,但是明显不能用,那么他们出现的原因是什么?是因为老方法方式现在被去掉了还是从VB或者F#那边直接copy的代码?导致了人云亦云?
在微软的官方资料中我获得的以及自身验证过的可用情况是这样的:
MicrosoftDocs:
catch (ArgumentException e) when (e.ParamName == "…")
{
然而在网络上却有大量这种文档:
摘自网络博客:
摘自网络博客:
相关连接:
疑惑之参考一
疑惑之参考二
疑惑之参考三
解析:
Lambda表达式:同上单选(3)、多选(15)。
Lambda表达式使用限制:
(1)在 is 或 as 运算符的左侧不允许使用 Lambda。
(2)适用于匿名方法的所有限制也适用于 Lambda 表达式。
匿名方法使用限制:
(1)匿名方法的参数的范围是“匿名方法块”。
(2)如果目标在块外部,那么,在匿名方法块内使用跳转语句(如 goto、break 或 continue)是错误的。 如果目标在块内部,在匿名方法块外部使用跳转语句(如 goto、break 或 continue)也是错误的。
(3)匿名方法不能访问外部范围的 ref 或 out 参数。
(4)在“匿名方法块”中不能访问任何不安全代码。
(5)在 is 运算符的左侧不允许使用匿名方法。