Home  >  Article  >  Backend Development  >  C# 7.0 new language features

C# 7.0 new language features

巴扎黑
巴扎黑Original
2017-04-29 17:26:521493browse

​ Below are instructions for scheduling functionality in the C# 7.0 language. Most of these features work in Visual Studio "15" Preview 4. Now is the best time to try it out, so please record your thoughts.

The C# 7.0 language adds many new features to focus on data consumption, simplified code and performance.

Perhaps the biggest feature is tuples, which makes it easy to have multiple results and thereby simplify the code is pattern matching conditional on the shape of the data. But there are also many other features that I hope to combine to make the code run more efficiently and clearly, allowing for more creativity. If there is something that is not running the way you want or if there are features you want to improve, use the "send feedback" function at the top of the Visual Studio window to feed back the results to us. Many of the features I described are not yet fully functional in Preview 4, and based on user feedback, we will add some new features when the final version is released. It must be pointed out that some features in existing plans may be changed or canceled in the final version.

If you are interested in this feature set and want to learn it, you can find many design instructions and related discussions on the Roslyn GitHub site.

Output (out) variable

Currently in C#, using out parameters is not as smooth as we imagine. When calling a method with an out parameter, you first have to declare the variable to pass to it. Although you would not normally initialize these variables (they will be overwritten after passing the method), nor can you use VAR to declare them, you need to specify the complete type:

public void PrintCoordinates(Point p)
{    int x, y; // have to "predeclare"
    p.GetCoordinates(out x, out y);
    WriteLine($"({x}, {y})");
}

In C# 7.0, we added the Out variable as the point passed as the out parameter to declare a variable:

public void PrintCoordinates(Point p)
{
    p.GetCoordinates(out int x, out int y);
    WriteLine($"({x}, {y})");
}

Note that the variables are within the scope of the enclosing block, so they can be used later. Most types of statements do not establish their own scope, so out variables are usually introduced into the enclosing scope in the declaration.

Note: In Preview 4, the scope rules are stricter: the scope of out variables is the statement in which they are declared. Therefore, the above example will not be used in subsequent versions.

Since out variables are declared directly as arguments passed to out parameters, the compiler can usually tell the type (unless there are conflicting overloads). So it's nice to declare them with VAR instead of a type:

p.GetCoordinates(out var x, out var y);

A common use of the out parameter is the Try... mode, where a boolean return in the out parameter indicates success, and the result obtained by the out parameter is:

public void PrintStars(string s)
{    if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }    else { WriteLine("Cloudy - no stars tonight!"); }
}

Note: Preview 4 handles it better by just defining it with an if statement.

Plan to allow "wildcards" as out parameters as well as in * form, ignoring unimportant out parameters:

p.GetCoordinates(out int x, out *); // I only care about x

Note: Whether wildcards can turn it into C# 7.0 is still unknown.

Pattern matching

C# 7.0 introduces the concept of mode. Abstractly speaking, this is a grammatical component that can be used to test whether a value has a certain "shape" and what is obtained from the value when it works. extra information.

The following is an example of patterns in C# 7.0:

  • The constant mode of c (c is a constant expression in C#) is used to test whether the input parameters are equal to c


  • The type pattern of T x (T is a type, x is an identifier) ​​is used to test whether the input parameter has type T. If so, extract the value of the input parameter into a new x variable of type T.


  • var x variable pattern (x is an identifier), usually matches and simply puts the value of the input parameter into a new variable x

This is a start, patterns are a new C# language element, and we can add more of them to C# in the future.

In C# 7.0, we are using patterns to enhance two existing language constructs:

  • is expressions can now have a pattern on the right hand side instead of just a type


  • case clauses in switch statements can now be matched by patterns, not just constant values

In the future of C#, we may add more places where patterns can be used.

Is expression with pattern

​This is an example of using an is expression with constant pattern and type pattern:

public void PrintStars(object o)
{    if (o is null) return;     // constant pattern "null"    if (!(o is int i)) return; // type pattern "int i"
    WriteLine(new string('*', i));
}

  正如你所看到的,模式变量(变量通过模式引入)与先前描述的 out 变量有些类似,他们可以在表达式中被声明,而且可以在它们最近的周围范围内被使用。也像 out 变量那样,模式变量是易变的,

: 就像 out 变量一样,严格的范围规则适用于 Preview 4.

  模式和 Try 方法通常会一起出现:

if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }

  带模式的 Switch 语句

  我们正在泛化 switch 语句,因此:

  • 你可以在任何类型上使用 switch(不仅仅是原始类型)


  • 可以在 case 子句中使用模式


  • Case 子句可以拥有额外的条件

  这里是一个简单的例子:

switch(shape)
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

  有几件关于这个新扩展的 switch 语句的事需要注意:

  • case 子句的顺序现在很重要:就像 catch 子句,case 子句不再是必然不相交的,第一个子句匹配的将被选择。因此这里重要的是上面代码中 square case 比 rectangle case 来得要早。也是和 catch 子句一样,编译器会通过标记明显不能到达的情况来帮助你。在这之前,你永远无法知道评价的顺序,所以这不是一个重大改变的特性。


  • 默认子句总是最后被评价:即使上面代码中 null 子句是最后才来,它会在默认子句被选择前被检查。这是为了与现有 switch 语义相兼容。然而,好的做法通常会让你把默认子句放到最后。


  • null 子句在最后不可到达:这是因为类型模式遵循当前的 is 表达式的例子并且不会匹配空值。这保证了空值不会偶然被任何的类型模式捎来第一抢购。你必须更明确如何处理它们(或为默认子句留下他们)。

  通过 case ...: 标签引入的模式变量仅存在于相对应的 switch 部分的范围内。

  元组

  这是常见的希望从一个方法返回多个值的做法。目前可用的选项不是最佳的:

  • Out 参数。使用笨拙(即便有上面描述到的提升),它们不使用异步的方法运行。


  • System.Tupleca6ded975897b2776a8a23c57d5020a5  返回类型。使用累赘并且需要一个元组对象的分配。


  • 为每个方法定制传输类型:大量的代码为了类型开销的目的仅是临时收集一些值


  • 匿名类型通过返回一个 dynamic 返回类型。高性能开销并且没有静态类型检查。

  为了在这方面做得更好,C# 添加了tuple types  tuple literals:

(string, string, string) LookupName(long id) // tuple return type
{
    ... // retrieve first, middle and last from data storage
    return (first, middle, last); // tuple literal
}

  这个方法目前有效地返回三个字符串,将其作为元素在元组类型里包裹起来。

  方法的调用者将会接受到一个元组,并且可以逐一访问元素。

var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");

  Item1 等等,是元组元素的默认名字,并能够经常被使用。但它们不是太好描述的,因此你可以选择性地添加更好的一个。

(string first, string middle, string last) LookupName(long id) // tuple elements have names

  现在元组的接受者拥有更多的可描述的名字用于运行:

var names = LookupName(id);
WriteLine($"found {names.first} {names.last}.");

  你也可以在 tuple literals 中直接指定名字:

return (first: first, middle: middle, last: last); // named tuple elements in a literal

  通常来说,你可以互相分配元组类型无关的名字,只要独立的元素是可以被分配的,元组类型会自如 转换成其他元组类型。特别是对于 tuple literals ,存在一些限制,这会警告或提示在常见的错误的情况下提示,例如偶然交换元素的名字。

注意:这些限制还没在 Preview 4 中实现

  元组是值类型,而且他们的元素只是公开、易变的域。他们的值相等,代表这两个元组是相等的(都有相同的哈斯码)如果它们的元素都结对匹配(都有相同的哈斯码)。

  这使得元组对于在多种返回值下的很多情况十分有用。举例来说,如果你需要一个有多种键的词典,使用元组作为你的键,然后一切东西就会如常工作。如果你需要在每个位置有一个有多种值的列表,使用元组,查找列表等等,程序会正常运行。

注意:元组依赖一系列底层类型,它们在 Preview 4 中不被引入。为了将来的工作,你可以通过 NuGget 轻易获取它们: 在 Solution Explorer 中右键点击项目,并选择“Manage NuGet Packages…” 选择“Browse”选项卡,检查“Include prerelease” 并且选择“nuget.org”作为“Package source” 搜索“System.ValueTuple”并安装它

  解构

  另一种消除元组(tuple)的方法是解构元组。通过一个解构声明语法,把一个元组(或者其他的值)拆分为几部分,并且重新定义为新的变量。

(string first, string middle, string last) = LookupName(id1); // deconstructing decla
rationWriteLine($"found {first} {last}.");

  在解构中可采用var关键字:

(var first, var middle, var last) = LookupName(id1); // var inside

  或者把var关键字提取出来,在括号外:

var (first, middle, last) = LookupName(id1); // var outside

  你也可以通过解构赋值来解构一个现有变量:

(first, middle, last) = LookupName(id2); // deconstructing assignment

  不仅仅元组可以被解构,任何类型都可以被解构,只要有一个对应的(实体或者扩展)解构方法:

public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }

  输出参数由解构之后的结果值构成。

  (为什么采用数据参数代替返回一个元组?这样,你可以重载多个不同的数值)

class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) { X = x; Y = y; }
    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

(var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);

  这将成为一种常见模式,包含析构函数和“对称”解析:

  针对输出变量,我们计划在解构中允许使用“通配符”:

(var myX, *) = GetPoint(); // I only care about myX
   注:仍然还没有确定是否将通配符引入C# 7.0中。

  局部函数

  有时,一个辅助函数只在一个使用它的单一方法内部有意义。现在你可以在其他功能体内部声明这些函数作为一个局部函数:

public int Fibonacci(int x)
{
    if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
    return Fib(x).current;

    (int current, int previous) Fib(int i)
    {
        if (i == 0) return (1, 0);
        var (p, pp) = Fib(i - 1);
        return (p + pp, p);
    }
}

  参数和闭合区间局部变量可用在局部函数内,类似lambda表达式。

  举一个例子,方法实现迭代器通常需要严格检查调用时非迭代器封装方法。(迭代器本身没有运行,只到调用MoveNext 才会运行)。局部函数在这种情况下是完美的:

public IEnumerable<T> Filter<T>(IEnumerable<T> source, Func<T, bool> filter)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (filter == null) throw new ArgumentNullException(nameof(filter));

    return Iterator();

    IEnumerable<T> Iterator()
    {
        foreach (var element in source) 
        {
            if (filter(element)) { yield return element; }
        }
    }
}

  如果迭代器是一个私有方法的下一个过滤器,它将有可能被其他成员不小心使用(没有参数检查)。此外,作为过滤器,它将需要采取所有的相同的参数,而不是指定域内的参数。

   注:在Preview 4版本中,本地函数必须在它们被调用之前声明。这个限制将被放松,能调用读取直接赋值的局部变量。

  Literal 改进

  C# 7.0 允许使用“_”作为数字分隔符在数字literals中:

var d = 123_456;
var x = 0xAB_CD_EF;

  你可以把它放在你想要的位置,提升可读性。这样对数值没有任何影响。

  此外,C# 7.0也介绍了二进制literals,这样你可以直接指定二进制模式而不必知道十六进制符号。

var b = 0b1010_1011_1100_1101_1110_1111;

  Ref 返回和本地

  就像你可以通过reference(用ref修饰符)在C#中传递东西,您现在可以通过reference return 他们,并通过 reference将它们存储在局部变量中。

public ref int Find(int number, int[] numbers)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers[i] == number) 
        {
            return ref numbers[i]; // return the storage location, not the value
        }
    }
    throw new IndexOutOfRangeException($"{nameof(number)} not found");
}

int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // aliases 7&#39;s place in the array
place = 9; // replaces 7 with 9 in the array
WriteLine(array[4]); // prints 9

  这对绕过占位符成为大数据结构是非常有用的。举例来说,一个游戏可能会在一个大的预分配数组结构中保存其数据(为避免垃圾收集暂停)。Methods 可以直接返回一个 reference 到这样一个结构,且通过调用者可以读取和修改它。

  这里有一些限制,以确保这是安全的:

  • 你可以只返回 refs 那些是 “安全返回(safe to return)”的:那些被传递给你的,和那些点到对象的字段。


  • Ref locals被初始化为某一存储位置,并且不能突变到指向另一个。

  广义异步返回类型

  截至目前为止,在C#调用异步方法必须要返回void,Task或Task8742468051c85b06f0a0af9e3e506b5c。C#7.0允许以这样的方式来定义其它类型,从而使它们可以从异步方法返回。

  例如,我们计划有一个ValueTask8742468051c85b06f0a0af9e3e506b5c结构类型。它是建立在预防Task8742468051c85b06f0a0af9e3e506b5c 对象的分配时,异步操作的结果是已在可等候的时间的情况下。对于很多异步场景,比如以涉及缓冲为例, 这可以大大减少分配的数量,并使性能有显著提升。

  这里有许多其他的方法可以让您想象自定义“task-like”类型是有用的。它不会是简单的正确创建,所以我们不要指望大多数人推出自己的,但他们很可能将会开始在框架和API展现出来,然后调用方可以返回并await他们今天做任务(Tasks)的方式。

    注:广义异步返回类型尚未应用在预览4。    

  更多 Expression-bodied 方法

  Expression-bodied 方法、属性等都是C# 6.0的重大突破,但并不允许他们在各种各样的member中使用,C#7.0添加了访问器、构造函数和终结器等,使更多member可以使用Expression-bodied 方法:

class Person
{
    private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
    private int id = GetId();

    public Person(string name) => names.TryAdd(id, name); // constructors
    ~Person() => names.TryRemove(id, out *);              // destructors
    public string Name
    {
        get => names[id];                                 // getters
        set => names[id] = value;                         // setters
    }
}
注:这些额外的Expression-bodied 方法还没有工作在预览4。

  这是一个由社区贡献的特征,而非微软C#团队。并且,开源!

  在表达式的中间抛出一个异常是很容易的,只需要为你自己调用一个方法,但在C#7.0中我们允许在一些地方直接抛出表达式:

class Person
{
    public string Name { get; }
    public Person(string name) => Name = name ?? throw new ArgumentNullException(name);
    public string GetFirstName()
    {
        var parts = Name.Split(" ");
        return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
    }
    public string GetLastName() => throw new NotImplementedException();
}
  注:抛出表达式还未在预览4工作。

  本文地址:http://www.oschina.net/translate/whats-new-in-csharp-7-0

  原文地址:https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

The above is the detailed content of C# 7.0 new language features. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn