C#各版本新功能 C#7.0

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);

有三种方法可用于析构元组:

  1. 可以在括号内显式声明每个字段的类型
    ( string city, int population, double area) = QueryCityData( "New York City" );
  2. 可使用 var 关键字,以便 C# 推断每个变量的类型
    var (city, population, area) = QueryCityData( "New York City" );// 将 var 关键字放在括号外

( string city, var population, var area) = QueryCityData( "New York City" ); // 将 var 关键字单独与任一或全部变量声明结合使用 不建议

  1. 可将元组析构到已声明的变量中
    [注意事项] (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 中提供了局部引用和引用返回,但为了防止滥用所以也有诸多约束

  1. 你不能将一个值分配给 ref 变量
ref int num = 10;   // error:无法使用值初始化按引用变量
  1. 你不能返回一个生存期不超过方法作用域的变量引用,如:
public ref int GetLocalRef(int num) => ref num;   // error: 无法按引用返回参数,因为它不是 ref 或 out 参数
  1. 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、void,现在 C# 7 中新增了一种类型:ValueTask,如下所示:

public async ValueTask<int> Func()
{
    await Task.Delay(100);
    return 5;
}

这样可以节省空间,尤其是在循环里面

posted @ 2019-03-14 14:45  maanshancss  阅读(589)  评论(0编辑  收藏  举报