>백엔드 개발 >C#.Net 튜토리얼 >C# 7.0의 새로운 언어 기능

C# 7.0의 새로운 언어 기능

巴扎黑
巴扎黑원래의
2017-04-29 17:26:521572검색

​다음은 C# 7.0 언어의 Planning 기능에 대한 설명입니다. 이러한 기능은 대부분 Visual Studio "15" Preview 4에서 작동합니다. 지금이 바로 시험해 볼 수 있는 가장 좋은 시기이니, 여러분의 생각을 기록해 주세요.

C# 7.0 언어에는 데이터 소비, 단순화된 코드 및 성능에 초점을 맞춘 많은 새로운 기능이 추가되었습니다.

아마도 가장 큰 특징은 튜플일 것입니다. 이는 여러 결과를 쉽게 얻을 수 있게 하고 그에 따라 코드를 단순화합니다. 이는 데이터 모양에 따른 조건부 패턴 일치입니다. 그러나 코드를 보다 효율적이고 명확하게 실행하여 더 많은 창의성을 발휘할 수 있도록 결합하고 싶은 다른 기능도 많이 있습니다. 원하는 대로 실행되지 않는 부분이 있거나 개선하고 싶은 기능이 있는 경우 Visual Studio 창 상단의 "피드백 보내기" 기능을 사용하여 결과를 피드백해 주세요. 제가 설명한 많은 기능은 아직 Preview 4에서 완벽하게 작동하지 않으며, 사용자 피드백을 바탕으로 최종 버전이 출시되면 몇 가지 새로운 기능을 추가할 예정입니다. 최종 버전에서는 기존 계획의 일부 기능이 변경되거나 취소될 수 있다는 점을 지적해야 합니다.

이 기능 세트에 관심이 있고 배우고 싶다면 Roslyn GitHub 사이트에서 다양한 디자인 지침과 관련 토론을 찾을 수 있습니다.

출력변수

현재 C#에서는 out 매개변수를 사용하는 것이 생각만큼 원활하지 않습니다. out 매개변수가 있는 메소드를 호출할 때 먼저 전달할 변수를 선언해야 합니다. 일반적으로 이러한 변수를 초기화하지 않거나(나중에 메소드에 의해 덮어쓰기됨) VAR을 사용하여 선언할 수 없지만 전체 유형을 지정해야 합니다.

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

C# 7.0에서는 변수를 선언하기 위해 out 매개 변수로 전달되는 지점인 Out 변수를 추가했습니다.

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

변수는 둘러싸는 블록의 범위 내에 있으므로 나중에 사용할 수 있습니다. 대부분의 문 유형은 자체 범위를 설정하지 않으므로 out 변수는 일반적으로 선언의 바깥쪽 범위에 도입됩니다.

참고: Preview 4에서는 범위 규칙이 더 엄격합니다. out 변수의 범위는 해당 변수가 선언된 문입니다. 따라서 위의 예는 이후 버전에서는 사용되지 않습니다.

out 변수는 out 매개 변수에 전달되는 인수로 직접 선언되므로 컴파일러는 일반적으로 (충돌하는 오버로드가 없는 한) 유형을 알 수 있습니다. 따라서 유형 대신 VAR을 사용하여 선언하는 것이 좋습니다.

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

out 매개변수의 일반적인 용도는 Try... 모드입니다. 이 모드에서는 out 매개변수의 부울 반환이 성공을 나타내며 out 매개변수로 얻은 결과는 다음과 같습니다.

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

참고: Preview 4에서는 if 문으로 정의하여 이를 더 잘 처리합니다.

중요하지 않은 출력 매개변수를 무시하고 * 형식뿐 아니라 출력 매개변수로도 "와일드카드"를 허용하도록 계획하세요.

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

참고: 와일드카드를 사용하여 C# 7.0으로 변환할 수 있는지 여부는 아직 알려지지 않았습니다.

패턴 매칭

C# 7.0에서는 모드 라는 개념을 도입했습니다. 추상적으로 말하면 이는 값이 특정 "모양"을 갖고 있는지 여부와 해당 값이 작동할 때 무엇을 얻는지 테스트하는 데 사용할 수 있는 문법적 구성 요소입니다. .

​다음은 C# 7.0의 패턴 예입니다.

  • c의 상수 모드(c는 C#의 상수 표현식)는 입력 매개변수가 c와 같은지 테스트하는 데 사용됩니다.


  • T x (T는 유형이고 x는 식별자)의 유형 패턴은 입력 매개변수의 유형이 T인지 테스트하는 데 사용됩니다. 그렇다면 입력 매개변수의 값을 T 유형의 새로운 x 변수로 추출합니다.


  • var x 변수 패턴(x는 식별자), 일반적으로 입력 매개변수의 값을 일치시키고 단순히 새 변수 x

에 넣습니다. 이것이 시작입니다. 패턴은 새로운 C# 언어 요소이며 앞으로 C#에 더 많은 패턴을 추가할 수 있습니다.

C# 7.0에서는 패턴을 사용하여 두 가지 기존 언어 구조를 향상합니다.

  • is 표현식은 이제 단순한 유형 대신 오른쪽에 패턴을 가질 수 있습니다.


  • 이제 switch 문의 case 절을 ​​상수 값뿐만 아니라 패턴으로 일치시킬 수 있습니다. ​​

C#의 미래에는 패턴을 사용할 수 있는 위치가 더 많이 추가될 수 있습니다.

패턴으로 표현인가요

​다음은 상수 패턴 및 유형 패턴과 함께 is 표현식을 사용하는 예입니다.

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/

위 내용은 C# 7.0의 새로운 언어 기능의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.