以下は、C# 7.0 言語でのプランニング機能の説明です。これらの機能のほとんどは、Visual Studio "15" Preview 4 で動作します。今がそれを試すのに最適な時期ですので、感想を記録してください。
C# 7.0 言語には、データ消費、コードの簡素化、パフォーマンスに焦点を当てた多くの新機能が追加されています。
おそらく最大の機能はタプルです。これにより、複数の結果を取得しやすくなり、データの形状に応じたパターン マッチングのコードが簡素化されます。しかし、コードをより効率的かつ明確に実行して、より創造性を発揮できるようにするために組み合わせたい機能は他にもたくさんあります。希望どおりに実行されていない点がある場合、または改善したい機能がある場合は、Visual Studio ウィンドウの上部にある「フィードバックの送信」機能を使用して結果をフィードバックしてください。ここで説明した機能の多くはプレビュー 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 変数は通常、宣言内の外側のスコープに導入されます。
注意: プレビュー 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 では、mode の概念が導入されました。これは、抽象的に言えば、値が特定の「形状」を持っているかどうか、および値が機能するときに値から取得される追加情報をテストするために使用できる文法コンポーネントです。
以下は C# 7.0 のパターンの例です:
c の定数モード (c は C# の定数式) は、入力パラメーターが c
に等しいかどうかをテストするために使用されます。 T x の型パターン (T は型、x は識別子) は、入力パラメーターが T 型であるかどうかをテストするために使用されます。 T 型である場合は、入力パラメーターの値を T 型の新しい x 変数に抽出します。
var x 変数パターン (x は識別子)、通常は一致し、入力パラメーターの値を新しい変数 x に単純に代入します
これは始まりであり、パターンは新しい C# 言語要素であり、将来的にはさらに多くのパターンを C# に追加できる可能性があります。
C# 7.0 では、パターンを使用して 2 つの既存の言語構造を強化しています:
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(不仅仅是原始类型)
可以在 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.Tuple<...> 返回类型。使用累赘并且需要一个元组对象的分配。
为每个方法定制传输类型:大量的代码为了类型开销的目的仅是临时收集一些值
匿名类型通过返回一个 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版本中,本地函数必须在它们被调用之前声明。这个限制将被放松,能调用读取直接赋值的局部变量。
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;
就像你可以通过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'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或Task
例如,我们计划有一个ValueTask
这里有许多其他的方法可以让您想象自定义“task-like”类型是有用的。它不会是简单的正确创建,所以我们不要指望大多数人推出自己的,但他们很可能将会开始在框架和API展现出来,然后调用方可以返回并await他们今天做任务(Tasks)的方式。
注:广义异步返回类型尚未应用在预览4。
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 中国語 Web サイトの他の関連記事を参照してください。