Maison > Article > développement back-end > Nouvelles fonctionnalités du langage C# 7.0
Ce qui suit est une description de la fonction de planification dans le langage C# 7.0. La plupart de ces fonctionnalités fonctionnent dans Visual Studio « 15 » Preview 4. C’est le meilleur moment pour l’essayer, alors veuillez enregistrer vos réflexions.
Le langage C# 7.0 ajoute de nombreuses nouvelles fonctionnalités pour se concentrer sur la consommation de données, le code simplifié et les performances.
La fonctionnalité la plus importante est peut-être les tuples, qui permettent d'obtenir facilement plusieurs résultats et simplifient ainsi le code. La correspondance de modèles dépend de la forme des données. Mais il existe également de nombreuses autres fonctionnalités que j'espère combiner pour rendre le code plus efficace et plus clair, permettant ainsi plus de créativité. S'il y a quelque chose qui ne fonctionne pas comme vous le souhaitez ou s'il y a des fonctionnalités que vous souhaitez améliorer, utilisez la fonction « envoyer des commentaires » en haut de la fenêtre de Visual Studio pour nous faire part des résultats. La plupart des fonctionnalités que j'ai décrites ne sont pas encore entièrement fonctionnelles dans la version préliminaire 4 et, sur la base des commentaires des utilisateurs, nous ajouterons de nouvelles fonctionnalités lors de la sortie de la version finale. Il faut souligner que certaines fonctionnalités des forfaits existants peuvent être modifiées ou annulées dans la version finale.
Si cet ensemble de fonctionnalités vous intéresse et souhaitez l'apprendre, vous pouvez trouver de nombreuses instructions de conception et discussions connexes sur le site Roslyn GitHub.
Actuellement en C#, l’utilisation des paramètres n’est pas aussi fluide qu’on l’imagine. Lors de l'appel d'une méthode avec un paramètre out, vous devez d'abord déclarer la variable à lui transmettre. Bien que vous n'initialisiez généralement pas ces variables (elles seront écrasées après avoir passé la méthode) et que vous ne puissiez pas non plus utiliser VAR pour les déclarer, vous devez spécifier le type complet :
public void PrintCoordinates(Point p) { int x, y; // have to "predeclare" p.GetCoordinates(out x, out y); WriteLine($"({x}, {y})"); }
En C# 7.0, nous avons ajouté la variable Out, qui est le point passé en paramètre out pour déclarer une variable :
public void PrintCoordinates(Point p) { p.GetCoordinates(out int x, out int y); WriteLine($"({x}, {y})"); }
Notez que les variables sont dans la portée du bloc englobant, elles peuvent donc être utilisées ultérieurement. La plupart des types d'instructions n'établissent pas leur propre portée, donc les variables out sont généralement introduites dans la portée englobante de la déclaration.
Remarque : Dans Preview 4, les règles de portée sont plus strictes : la portée des variables out est la portée dans laquelle elles sont déclarées. Par conséquent, l’exemple ci-dessus ne sera pas utilisé dans les versions ultérieures.
Étant donné que les variables out sont déclarées directement en tant qu'arguments transmis aux paramètres out, le compilateur peut généralement en indiquer le type (sauf en cas de surcharges conflictuelles). Il est donc bon de les déclarer en utilisant des VAR au lieu d'un type :
p.GetCoordinates(out var x, out var y);
Une utilisation courante du paramètre out est le mode Try..., où un retour booléen dans le paramètre out indique le succès, et le résultat obtenu par le paramètre out est :
public void PrintStars(string s) { if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); } else { WriteLine("Cloudy - no stars tonight!"); } }
Remarque : L'aperçu 4 le gère mieux en le définissant simplement avec une instruction if.
Plan autorise les "caractères génériques" comme paramètres de sortie ainsi que sous forme *, ignorant les paramètres de sortie sans importance :
p.GetCoordinates(out int x, out *); // I only care about x
Remarque : On ne sait toujours pas si les caractères génériques peuvent le transformer en C# 7.0.
C# 7.0 introduit le concept de mode De manière abstraite, il s'agit d'un composant grammatical qui peut être utilisé pour tester si une valeur a une certaine « forme » et ce qui est obtenu à partir de la valeur lorsqu'elle fonctionne. .
Ce qui suit est un exemple de modèles en C# 7.0 :
Le mode constant de c (c est une expression constante en C#) est utilisé pour tester si le paramètre d'entrée est égal à c
Le modèle de type de T x (T est un type et x est un identifiant) est utilisé pour tester si le paramètre d'entrée est de type T. Si tel est le cas, extrayez la valeur du paramètre d'entrée dans une nouvelle variable x de type T.
modèle de variable var x (x est un identifiant), correspond généralement et met simplement la valeur du paramètre d'entrée dans une nouvelle variable x
C'est un début, les modèles sont un nouvel élément du langage C#, et nous pourrons en ajouter davantage en C# à l'avenir.
En C# 7.0, nous utilisons des modèles pour améliorer deux constructions de langage existantes :
Ces expressions peuvent désormais avoir un motif sur le côté droit au lieu d'un simple type
les clauses case dans les instructions switch peuvent désormais correspondre à des modèles, pas seulement à des valeurs constantes
Dans le futur de C#, nous pourrions ajouter davantage d’endroits où les modèles pourront être utilisés.
Voici un exemple d'utilisation d'une expression is avec un modèle constant et un modèle de type :
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.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版本中,本地函数必须在它们被调用之前声明。这个限制将被放松,能调用读取直接赋值的局部变量。
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或Task8742468051c85b06f0a0af9e3e506b5c。C#7.0允许以这样的方式来定义其它类型,从而使它们可以从异步方法返回。
例如,我们计划有一个ValueTask8742468051c85b06f0a0af9e3e506b5c结构类型。它是建立在预防Task8742468051c85b06f0a0af9e3e506b5c 对象的分配时,异步操作的结果是已在可等候的时间的情况下。对于很多异步场景,比如以涉及缓冲为例, 这可以大大减少分配的数量,并使性能有显著提升。
这里有许多其他的方法可以让您想象自定义“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/
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!