out 变量
(以前必须在外面定义一行才可以使用)
if (int.TryParse(input, out int result))
Console.WriteLine(result);
元组
元组(Tuple)在 .Net 4.0 的时候就有了,但元组也有些缺点,如:
1)Tuple 会影响代码的可读性,因为它的属性名都是:Item1,Item2.. 。
2)Tuple 还不够轻量级,因为它是引用类型(Class)。
备注:上述所指 Tuple 还不够轻量级,是从某种意义上来说的或者是一种假设,即假设分配操作非常的多。
C# 7 中的元组(ValueTuple)解决了上述两个缺点:
1)ValueTuple 支持语义上的字段命名。
2)ValueTuple 是值类型(Struct)。
- 如何创建一个元组?
var tuple = (1, 2); //使用语法糖创建元组
var tuple2 = ValueTuple.Create(1, 2); // 使用静态方法【Create】创建元组
var tuple3 = new ValueTuple<int, int>(1, 2); // 使用 new 运算符创建元组
WriteLine($"first:{tuple.Item1}, second:{tuple.Item2}, 上面三种方式都是等价的。");
- 如何创建给字段命名的元组?
以前的元组元素只能是item1 itme2 ,现在可以起有意义的名字了
// 左边指定字段名称
(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");
//右边指定字段名称
var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");
// 左右两边同时指定字段名称
(int one, int two) tuple3 = (first: 1, second: 2); /* 此处会有警告:由于目标类型(xx)已指定了其它名称,因为忽略元组名称xxx */
WriteLine($"first:{tuple3.one}, second:{tuple3.two}");
//注:左右两边同时指定字段名称,会使用左边的字段名称覆盖右边的字段名称(一一对应)。
//原理解析:上述给字段命名的元组在编译后其字段名称还是:Item1, Item2...,即:“命名”只是语义上的命名
析构元组 (Deconstructing tuples)
public class Example
{
public static void Main()
{
var result = QueryCityData("New York City"); //之前 我需要定义三个变量去接收这个值
var city = result.Item1;
var pop = result.Item2;
var size = result.Item3
// Do something with the data.
}
private static (string, int, double) QueryCityData(string name)
{
if (name == "New York City")
return (name, 8175133, 468.48);
return ("", 0, 0);
}
}
// 现在单个操作中解包一个元组中的所有项
(int max, int min) = Range(numbers);// 析构元组,定义一个变量就可以了,比上面定义三行要简单明了
Console.WriteLine(max);
有三种方法可用于析构元组:
- 可以在括号内显式声明每个字段的类型
( string city, int population, double area) = QueryCityData( "New York City" ); - 可使用 var 关键字,以便 C# 推断每个变量的类型
var (city, population, area) = QueryCityData( "New York City" );// 将 var 关键字放在括号外
( string city, var population, var area) = QueryCityData( "New York City" ); // 将 var 关键字单独与任一或全部变量声明结合使用 不建议
- 可将元组析构到已声明的变量中
[注意事项] (https://docs.microsoft.com/zh-cn/dotnet/csharp/deconstruct)
使用弃元析构元组
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears( "New York City" , 1960 , 2010 );
Console.WriteLine( $"Population change, 1960 to 2010: {pop2 - pop1:N0} " );
析构用户自定义类型
作为类,解构或者接口的创建者,可以通过一个或者多个 Deconstruct 方法来析构该类型的实例,该方法返回void,并且析构的每一个值都需要用out参数标志;
public class Point
{
public Point(double x, double y)
=> (X, Y) = (x, y);
public double X { get; }
public double Y { get; }
public void Deconstruct(out double x, out double y) =>
(x, y) = (X, Y);
}
//You can extract the individual fields by assigning a Point to a tuple:
var p = new Point(3.14, 2.71);
(double X, double Y) = p;
使用弃元析构用户类型
var (fName, _, city, _) = p;
Console.WriteLine( $"Hello {fName} of {city} !" );
使用扩展方法析构用户自定义类型
弃元
使用弃元析构元组和自定义类型
public static void Main()
{//我只需要第四个值跟第6个值 其它的我不关心;
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);
Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
}
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
int population1 = 0, population2 = 0;
double area = 0;
if (name == "New York City")
{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}
return ("", 0, 0, 0, 0, 0);
}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149
当使用外参的时候
string l_strDate = "2019-01-01";
if (DateTime.TryParse(l_strDate,out _)) //以前需要写..
{
Console.WriteLine(DateTime.Parse(l_strDate));
}
在模式匹配操作 is switch语句中
public static void Main()
{
object[] objects = { CultureInfo.CurrentCulture,
CultureInfo.CurrentCulture.DateTimeFormat,
CultureInfo.CurrentCulture.NumberFormat,
new ArgumentException(), null };
foreach (var obj in objects)
ProvidesFormatInfo(obj);
}
private static void ProvidesFormatInfo(object obj)
{
switch (obj)
{
case IFormatProvider fmt:
Console.WriteLine($"{fmt} object");
break;
case null:
Console.Write("A null object reference: ");
Console.WriteLine("Its use could result in a NullReferenceException");
break;
case object _:
Console.WriteLine("Some object type without format information");
break;
}
}
数字,二进制的分隔符
增强文本可读性
// 二进制文本:
public const int Sixteen = 0b0001_0000;
// 数字分隔符:
public const long BillionsAndBillions = 100_000_000_000;
独立弃元
private static void ShowValue(int _)
{
byte[] arr = { 0, 0, 1, 2 };
_ = BitConverter.ToInt32(arr, 0);
Console.WriteLine(_);
}
模式匹配
现在可以在匹配一个类型时,自动转换为这个类型的变量,如果转换失败,这个变量就赋值为默认值(null或0)
if (input is int count) //
sum += count;
public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
int sum = 0;
foreach (var i in sequence)
{
switch (i)
{
case 0:
break;
case IEnumerable<int> childSequence: //模式匹配 匹配是 IEnumerable<int> 类型的
{
foreach(var item in childSequence)
sum += (item > 0) ? item : 0;
break;
}
case int n when n > 0: //模式匹配 匹配是int 类型的
sum += n;
break;
case null: //is the null pattern
throw new NullReferenceException("Null found in sequence");
default:
throw new InvalidOperationException("Unrecognized type");
}
}
return sum;
}
局部变量和返回结果
我们知道 C# 的 ref 和 out 关键字是对值传递的一个补充,是为了防止值类型大对象在Copy过程中损失更多的性能。现在在C# 7中 ref 关键字得
到了加强,它不仅可以获取值类型的引用而且还可以获取某个变量(引用类型)的局部引用。如:
static ref int GetLocalRef(int[,] arr, Func<int, bool> func)
{
for (int i = 0; i < arr.GetLength(0); i++)
{
for (int j = 0; j < arr.GetLength(1); j++)
{
if (func(arr[i, j]))
{
return ref arr[i, j];
}
}
}
throw new InvalidOperationException("Not found");
}
int[,] arr = { { 10, 15 }, { 20, 25 } };
ref var num = ref GetLocalRef(arr, c => c == 20);
num = 600;
Console.WriteLine(arr[1, 0]); //output 600
使用方法:
1.方法的返回值必须是引用返回
- 声明方法签名时必须在返回类型前加上 ref 修饰
- 每个 return 关键字后也要加上 ref 修饰,以表明是返回引用
2.分配引用(即赋值),必须在声明局部变量前加上 ref 修饰,以及在方法返回引用前加上 ref 修饰。
注:C# 开发的是托管代码,所以一般不希望程序员去操作指针。并由上述可知在使用过程中需要大量的使用 ref 来标明这是引用变量(编译后其实没那么多),当然这也是为了提高代码的可读性。
总结:虽然 C# 7 中提供了局部引用和引用返回,但为了防止滥用所以也有诸多约束
- 你不能将一个值分配给 ref 变量
ref int num = 10; // error:无法使用值初始化按引用变量
- 你不能返回一个生存期不超过方法作用域的变量引用,如:
public ref int GetLocalRef(int num) => ref num; // error: 无法按引用返回参数,因为它不是 ref 或 out 参数
- ref 不能修饰 “属性” 和 “索引器”。
var list = new List<int>();
ref var n = ref list.Count; // error: 属性或索引器不能作为 out 或 ref 参数传递
局部函数(Local functions)
一个函数在另外一个函数的里面
public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
if (start < 'a' || start > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
if (end < 'a' || end > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");
if (end <= start)
throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
return alphabetSubsetImplementation();
IEnumerable<char> alphabetSubsetImplementation()
{
for (var c = start; c < end; c++)
yield return c;
}
}
更多的表达式方法体
C# 6 的时候就支持表达式体成员,但当时只支持“函数成员”和“只读属性”,这一特性在C# 7中得到了扩展,它能支持更多的成员:构造函数
、析构函数、带 get,set 访问器的属性、以及索引器。如下所示
// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;
// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
private string label;
// Expression-bodied get / set accessors.
public string Label
{
get => label;
set => this.label = value ?? "Default label";
}
// Expression-bodied indexers
public string this[string name] => Convert.ToBase64String(Encoding.UTF8.GetBytes(name));
Throw expressions
throw之前必须是一个语句,因此有时不得不写更多的代码来完成所需功能。但7.0提供了throw表达式来使代码更简洁,阅读更轻松。
void Main()
{
// You can now throw expressions in expressions clauses.
// This is useful in conditional expressions:
string result = new Random().Next(2) == 0 ? "Good" : throw new Exception ("Bad");
result.Dump();
Foo().Dump();
}
public string Foo() => throw new NotImplementedException();
扩展异步返回类型(Generalized async return types)
以前异步的返回类型必须是:Task、Task
public async ValueTask<int> Func()
{
await Task.Delay(100);
return 5;
}
这样可以节省空间,尤其是在循环里面