C#6.0新用法总结

作者 Zhendong Ho 日期 2018-11-23
C#
C#6.0新用法总结

字符串插值

通过使用字符串插值格式,字符串可以支持嵌入的表达式。

基本用法

字符串插值语法在一个字符串字面量前加上一个“$”符号前缀,然后将表达式嵌入大括号中。

System.Console.WriteLine($"Your full name is {firstName} {lastName}.");

字符串字面量可以通过在“@”符号前加上“$”符号的字符串插值组合而成。

System.Console.WriteLine($@"Your full name is:
{firstName} {lastName}");//输出被分为了两行

如果在代码中,在大括号后换行,就可以避免输出被分为两行。

System.Console.WriteLine($@"Your full name is: {
firstName} {lastName}");

深入理解

字符串插值是调用string.Format()方法的简写。

System.Console.WriteLine($"Your full name is {firstName} {lastName}.")
//等价于
object[] args = new object[] { firstName, lastName };
Console.WriteLine(string.Format("Your full name is {0} {1}.", args));

using和using static

using static指令必须放在文件的最开始。以后每次使用可以省略命名空间和类型名的前缀。

using static System.Console;
class HeyYou
{
static void Main()
{
string firstName;
string lastName;

WriteLine("Hey you!");//可省略类型名
Write("Enter your firstName: ");//可省略类型名

firstName = ReadLine();
Write("Enter your lastName: ");
lastName = ReadLine();
WriteLine(
$("Your full name is {firstName} {lastName}.");
)
}
}

换行符

输出新行所需的字符串取决于执行代码的操作系统。

  • Windows平台换行符:\r\n

  • UNIX系统:\n

System.Environment.NewLine可用于创建空行。

System.Console.WriteLine("Hello world")
//等价于
System.Write($"Hello world{System.Environment.NewLine}");

隐式局部变量和匿名类型

C#3.0新增了关键字var来声明隐式类型的局部变量。var可以配合匿名类型使用。

匿名类型是在方法内部动态声明的数据类型,而不是通过显式的类定义来声明的。

class Program
{
static void Main()
{
var patent1 = new { Title = "Bifocals", YearOfPublication = "1784" };
var patent2 = new { Title = "Photograph", YearOfPublication = "1877" };

System.Console.WriteLine($"{patent1.Title}{patent1.YearOfPublication})");
//Bifocals(1784)
System.Console.WriteLint($"{patent2.Title}{patent2.YearOfPublication})");
//Photograph(1877)
}
}

可空修饰符

一般情况下,不能将null值赋给值类型。但可以用可空修饰符“?”声明可以存储null的变量。

static void Main()
{
int? count = null;
do
{
//...
}
while(count == null);
}

数据类型之间转换

checked和unchecked转换

C#提供了特殊关键字来标识代码块,指出假如目标数据类型太小,以至于容不下赋值的数据,会发生什么情况。

将发生溢出的代码放到一个checked块中,或者在编译时使用checked选项,就会在运行时引发System.OverflowException异常。

static void Main(string[] args)
{
checked
{
//int.MaxValue equals 2147483647
int n = int.MaxValue;
n = n + 1;
Console.WriteLine(n);
}
}

若把发生溢出的代码放到unchecked块中,它强制不进行溢出检查,不会为块中溢出的赋值引发异常。

unchecked
{
//int.MaxValue equals 2147483647
int n = int.MaxValue;
n = n + 1;
Console.WriteLine(n);
}

TryParse

C#2.0开始,所有基础元数据数值类型都包含静态方法TryParse(),转换失败的时候,不引发异常,而是返回false。

double number;
string input;

System.Console.WriteLine("Enter a number: ");
input = System.Console.ReadLine();
if (double.TryParse(input, out number))
{
//Convert correctly, now use number
//...
}
else
{
System.Console.WriteLine("The text enter was not a valid number.");//转换失败
}

数组

default关键字

使用default操作符显式地获取任何数据类型的默认值

int count = default(int);//0

注意:从C#2.0开始可以使用default()表达式判断数据类型的默认值。default()获取数据类型作为参数。

  • 引用类型(如string)默认值为null。
  • 数值类型默认值为0。
  • bool类型默认值为false。
  • char默认值为\0。

二维数组

声明多维数组要使用逗号,逗号数量+1=维数。

声明二维数组

int[,] cells = int[3,3];

初始化二维整数数组

int[,] cells = {
{1, 0, 2},
{1, 2, 0},
{1, 2, 1}
};

交错数组

多维数组的每一维的大小都必须一致。交错数组是由数组构成的数组,不需要具有一致的大小。

初始化交错数组

int[][] cells = {
new int[]{1, 0, 2, 0},
new int[]{1, 2, 0},
new int[]{1, 2},
new int[]{1}
};

注意:交错数组要求对数组中的数组进行实例化。

数组实例方法

获取特定维的大小:GetLength()

bool[,,] cells;
cells = new bool[2,3,3];
System.Console.WriteLint(cells.GetLength(0));//Display 2

表示第一维上的元素个数为2。

获取整个数组的维数:Rank

System.Console.WriteLine(cells.Rank);//Display 3

表示该数组是三维数组。

注意:要创建数组的全新副本,需要使用数组的Clone()方法。该方法返回数组的一个副本。

操作符

空接合操作符

空接合操作符??是二元操作符,支持短路求值。表示“如果这个值为空,就使用另一个值”。

string fileName = GetFileName();
//...
string fullName = fileName ?? "default.txt";

注意:空接合操作符是C#2.0和可空值类型一起引入的。它的操作数既可以是可空值类型,也可以是引用类型。

null条件操作符

在调用值为null的方法时,运行时会抛出一个System.NullReferenceException的异常。C#6.0引入了一种简化的null条件操作符?.来替代调用成员前进行null值检查的操作。

class Program
{
static void Main(string[] args)
{
if (args?.Length == 0)//如果args不为null,则arg?.Length返回int?类型,否则返回null
{
System.Console.WriteLine("ERROR");
}
else
{
/*
错误,如果args不为null,但是数组为空数组,会报错索引超出了数组界限。
如果args为null,会引发System.NullReferenceException异常。
如果args为null,args?[0].ToUpper()的写法不会引发System.NullReferenceException异常。
*/
if (args[0]?.ToLower().StartWith("file:") ?? false)
{
string fileName = args[0]?.Remove(0, 5);
}
}

}
}

注意:当调用的成员返回的是一个值类型时,它总是返回该类型的可为空的版本。

例如,args?.Length返回int?,而不是简单的int。

类似的,args[0]?.ToLower().StartWith(“file:”)返回bool?。

按位操作符

移位操作符

左移位:<<

左移时,所有位都向左移动由操作符右侧的操作数指定的位数。移位后在右边留下的空位填充0。

右移位:>>

右移位原理和左移位相似,是朝相反方向移位,但如果是负数,左侧填充1而不是0。

复合移位和赋值操作符:<<=和>>=

int x;
x = (-7 >> 2);//1111 1111 1111 1111 1111 1111 1111 1001
//右移位后 1111 1111 1111 1111 1111 1111 1111 1110
System.Console.WriteLine($("x = {x}."));//x = -2

按位操作符

对两个操作数执行逐位的逻辑运算。比如AND、OR和XOR等,使用&、|和^操作符实现。

byte and, or, xor;
and = 12 & 7; // and = 4
or = 12 | 7; // or = 15
xor = 12 ^ 7; // xor = 11
System.Console.WriteLine($"and = {and} \nor = {or} \nxor = {xor}");

上述代码中的7称为掩码,作用是通过特定的操作符表达式,公开或消除第一个操作数中特定的位。

注意:AND和OR操作符的按位版本(&和|)不会进行短路求值。

获取二进制形式的字符串表示

class BinaryConverter
{
static void Main()
{
const int size = 64;
ulong value;
char bit;

System.Console.Write("Enter a integer: ");
// Use Long.Parse() to support negative number
// Assumes unchecked assignment to ulong.
value = (ulong)long.Parse(System.Console.ReadLine());

// Set initial mask to 100....
ulong mask = 1UL << size - 1;
for (int count = 0; count < size; count++)
{
bit = ((mask & value) != 0) ? '1': '0';
System.Console.Write(bit);
// Shift mask one Location over to the right
mask >>= 1;
}
System.Console.WriteLine();
}
}

按位赋值操作符

按位操作符可以和赋值操作符合并,即&=、|=和^=。

byte and = 12, or = 12, xor = 12;
and &= 7; // and = 4
or |= 7; // or = 15
xor ^= 7; // xor = 11

按位取反操作符

按位取反操作符(~)是对操作数的每一位取反。

C#预处理指令

控制流语句中的条件表达式在运行时求值。相反,C#预处理器在编译时调用。预处理指令告诉C#编译器要编译哪些代码,并指出如何处理代码中的特定错误和警告。C#预处理指令还可告诉C#编译器有关代码组织的信息。

每个预处理指令都以#开头,而且必须在一行中写完。换行符标志着预处理指令的结束。

语句或表达式 常规语法结构 示例
#if指令 #if preprocessor-expression
    code
#endif
#if CSHARP2PLUS
    Console.Clear();
#endif
#elif指令 #if preprocessor-expression1
    code
#elif preprocessor-expression2
    code
#endif
#if LINUX

#elif WINDOWS

#endif
#else指令 #if
    code
#else
    code
#endif
#if CSHARP1

#else

#endif
#define指令 #define conditional-symbol #define CSHARP2PLUS
#undef指令 #undef conditional-symbol #undef CSHARP2PlUS
#error指令 #error preproc-message #error Buggy implementation
#warning指令 #warning preproc-message #warning Needs code review
#pragma指令 #pragma warning #pragma warning disable 1030
#line指令 #line org-line new-line #line 467
“TicTacToe.cs”
#line default #line default
#region指令 #region preproc-message
    code
#endregion
#region
    …
#endregion

排除和包含代码

或许最常用的预处理指令就是控制何时以及如何包含代码的指令。

例如,要使代码兼容C#2.0(及以后版本)和C#1.0的编译器,可以使用一个预处理指令指示在遇到1.0编译器时排除C#2.0特有的代码。

#if CSHARP2PLUS
System.Console.Clear();
#endif

System.Console.Clear()方法,是CLI2.0或更高版本才支持的方法。是用#if和#endif预处理指令,这行代码只有在定义了预处理符号CSHARP2PLUS的前提下才会编译。

#define指令

#define指令,可以把它看作声明一个变量。具体和代码中声明变量又有如下区别:

  • #define指令必须在代码文件中的第一句。
  • 没有分号作为结束标记。
  • 单独使用是没有任何意义的,但是和#if,#elif,#else,#endif一起使用就截然不同了。

可以采用两种方式定义预处理符号。第一种方式是使用#define指令。

#define CSHARP2PLUS
using System;

namespace PreprocessDemo
{
class Program
{
static void Main(string[] args)
{
#if CSHARP2PLUS
Console.WriteLine("CSHARP2PLUS");
#endif
Console.ReadKey();
}
}
}

第二种方式是在编译时使用define选项。

>csc.exe /define:CSHARP2PLUS TicTacToe.cs

用Mono编译器实现上面相同的功能。

>mcs.exe -define:CSHARP2PLUS TicTacToe.cs

多个定义以分号分隔。使用define编译器选项的优点是不需要更改源代码,所以可用相同的源代码文件生成两套不同的二进制程序。

要取消符号定义,可以采用和#define相同的方式来使用#undef指令。

生成错误和警告

可以插入#error和#warning指令来分别生成错误和警告消息。

#warning "Some move allowed multiple times."

包含#warning指令后,编译器会主动发出警告。利用警告的信息,可以标记出代码中潜在的Bug和可以改进的部分。它是提醒开发者任务尚未完结的好帮手。

关闭警告信息

警告可以指出代码中可能存在的问题。然而有的警告可以合理地忽略。C#2.0以及之后的编译器提供了预处理指令#pragma来关闭或还原警告。

使用预处理指令#pragma禁用#warning指令

#pragma warning disabled 1030

使用预处理指令#pragma还原警告

#pragma warning restore 1030

nowarn:<warn list>选项

除了#pragma指令,C#编译器通常还支持nowarn:<warn list>选项。它可以获得与#pragma相同的效果,只是不用把它加进源代码,而是把它作为编译器选项使用。nowarn选项会影响整个编译过程,而#pragma选项只影响该指令所在的文件。

>csc /doc:generate.xml /nowarn:1591 /out:generate.exe Program.cs

指定行号

利用#line指令可以改变C#编译器在报告错误或警告时显示的行号。真实行号显示在最左侧。

#line 113 "TicTacToe.cs"
#warning "Some move allowed multiple times."
#line default
  • 使用#line指令后,编译器会将实际发生在2行的警告报告在113行上发生。
  • 在#line指令后添加default,会反转之前所有的#line的效果,并指示编译器报告真实的行号,而不是之前使用#line指定的行号。

可视编辑器提示

C#提供了只有在可视代码编辑器中才有用的两个预处理指令,也就是#region和#endregion。像Microsoft Visual Studio这样的可视代码编辑器能够搜索源代码,找到这些指令,并相应地进行处理。