2月12日消息,据微软中国MSDN消息,C#10已作为.NET的一部分发布6和VisualStudio2022。在本文中,Microsoft将介绍C#10的许多新功能,这些功能可以让您的代码更漂亮、更具表现力和更快。阅读VisualStudio2022公告和.NET6公告以了解更多信息,包括如何安装。VisualStudio2022公告https://aka.ms/vs2022gablog.NET6https://aka.ms/dotnet6-GA全局和隐式usingsusing指令简化了命名空间的使用方式。C#10包含一个新的全局using指令和隐式usings,以减少您需要在每个文件顶部指定的usings数量。全局using指令如果关键字global出现在using指令之前,using适用于整个项目:globalusingSystem;您可以在全局using指令中使用using的任何函数。例如,添加静态导入类型并使该类型的成员和嵌套类型在整个项目中可用。如果您在using指令中使用别名,该别名也会影响您的整个项目:globalusingstaticSystem.Console;globalusingEnv=System.Environment;您可以将全局使用放在任何.cs文件中,包括Program.cs或专门命名的文件,例如globalusings.cs。globalusings的范围是当前编译,一般对应当前项目。有关详细信息,请参阅全局使用指令。全局使用指令https://docs.microsoft.com/dotnet/csharp/languagereference/keywords/using-directive#global-modifier隐式使用隐式使用功能会自动为您正在构建的项目类型添加通用全局使用指令。要启用隐式使用,请在.csproj文件中设置ImplicitUsings属性:enableImplicitisenabledinthenew.NET6templatesFormulausings.在这篇博文中阅读有关.NET6模板更改的更多信息。一些特定的全局using指令集取决于您正在构建的应用程序类型。例如,控制台应用程序或类库的隐式使用不同于ASP.NET应用程序的隐式使用。有关详细信息,请参阅这篇隐式使用文章。博客文章https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-7/#net-sdk-c-project-templates-modernizedimplicitusingshttps://docs.microsoft.com/en-us/dotnet/core/project-sdk/overview#implicit-using-directivesCombiningusing功能文件顶部的传统using指令、全局using指令和隐式using可以很好地协同工作。隐式使用允许您在项目文件中包含适合您正在构建的项目类型的.NET命名空间。全局using指令允许您包含其他名称空间以使其在整个项目中可用。代码文件顶部的using指令允许您包含仅供项目中的几个文件使用的名称空间。无论它们是如何定义的,附加的using指令都会增加名称解析中出现歧义的可能性。如果是这种情况,请考虑添加别名或减少要导入的名称空间的数量。例如,您可以在文件子集的顶部用显式using指令替换全局using指令。如果您需要移除通过隐式使用包含的名称空间,您可以在项目文件中指定它们:您也可以添加名称空间,就像它们一样作为全局using指令,您可以将Using项添加到项目文件中,例如:从C#10开始,您可以将命名空间作为语句包括在内,后跟一个分号且不带花括号:namespaceMyCompany.MyNamespace;classMyClass//Note:noindentation{...}这简化了代码并删除了嵌套级别。只允许一个文件范围命名空间声明,并且它必须出现在任何类型声明之前。有关文件范围命名空间的更多信息,请参阅命名空间关键字一文。命名空间关键字文章https://docs.microsoft.com/dotnet/csharp/languagereference/keywords/namespace对lambda表达式和方法组的改进Microsoft对lambda的语法和类型进行了多项改进。Microsoft期望这些将广泛有用,其中一项推动举措是使ASP.NETMinimalAPI更简单。Lambda语法https://docs.microsoft.com/dotnet/csharp/whats-new/csharp-10#lambda-expression-improvementsASP.NET最小APIhttps://devblogs.microsoft.com/dotnet/announcing-asp-net-core-in-net-6/lambda的自然类型Lambda表达式现在有时具有“自然”类型。这意味着编译器通常可以推断出lambda表达式的类型。到目前为止,lambda表达式必须转换为委托或表达式类型。在大多数情况下,您将使用BCL中重载的Func<...>或Action<...>委托类型之一:Funcparse=(strings)=>int.Parse(s);但是,从C#10开始,如果lambda没有这样的“目标类型”,Microsoft将尝试为您计算一个:varparse=(strings)=>int.Parse(s);您可以在您最喜欢的编辑器中执行此操作在浏览器中将鼠标悬停在varparse上,您会看到类型仍然是Func。通常,编译器将使用可用的Func或Action委托(如果存在合适的委托)。否则,它将合成一个委托类型(例如,当您有ref参数或有大量参数时)。并非所有lambda表达式都具有自然类型——有些只是没有足够的类型信息。例如,删除参数类型将使编译器无法决定使用哪种委托类型:varparse=s=>int.Parse(s);//ERROR:notenoughtypeinfointhelambdalambda'snaturaltype意味着它们可以分配给较弱的类型,例如objectOrDelegate:objectparse=(strings)=>int.Parse(s);//FuncDelegateparse=(strings)=>int.Parse(s);//Func在表达树的时候,微软结合了“target”和“natural”类型。如果目标类型是LambdaExpression或非泛型表达式(所有表达式树的基本类型)并且lambda具有自然委托类型D,Microsoft将生成Expression代替:LambdaExpressionparseExpr=(strings)=>int.Parse(s);//Expression>ExpressionparseExpr=(strings)=>int.Parse(s);//Expression>方法组的自然类型方法组(也就是说,没有参数列表方法名称)现在有时也有自然类型。您始终可以将方法组转换为兼容的委托类型:Funcread=Console.Read;Actionwrite=Console.Write;现在,如果一个方法组只有一个重载,那么它将有一个自然类型:lambda的类型在前面的示例中,lambda表达式的返回类型是显而易见的,并且可以从中推断出来。情况并非总是如此:varchoose=(boolb)=>b?1:"two";//ERROR:Can'tinferreturntype在C#10中,您可以在lambda表达式上指定显式返回类型,就像在方法上一样或本机功能相同。返回类型在参数之前。当您指定显式返回类型时,参数必须括在括号中,这样编译器或其他开发人员就不会太混淆:varchoose=object(boolb)=>b?1:"two";//Funclambda上的属性从C#10开始,您可以将属性放在lambda表达式上,就像方法和局部函数一样。当有属性时,lambda的参数列表必须用括号括起来:Funcparse=[Example(1)](s)=>int.Parse(s);varchoose=[Example(2)][例子(3)]object(boolb)=>b?1:"two";就像本地函数一样,如果属性在AttributeTargets.Method上有效,则可以将它们应用于lambda。Lambda的调用方式与方法和局部函数不同,因此调用lambda时属性无效。但是,lambda上的属性对于代码分析仍然有用,并且可以通过反射发现它们。结构的改进C#10引入了结构的功能,这些功能在结构(结构)和类之间提供了更好的奇偶校验。这些新功能包括无参数构造函数、字段初始值设定项、记录结构和with表达式。01无参数结构构造函数和字段初始值设定项在C#10之前,每个结构都有一个隐式公共无参数构造函数,用于将结构的字段设置为默认值。在结构上创建无参数构造函数是错误的。从C#10开始,您可以包含自己的无参数结构构造函数。如果您不提供,将提供一个隐式无参数构造函数来将所有字段设置为默认值。您在结构中创建的无参数构造函数必须是公共的而不是部分的:publicstructAddress{publicAddress(){City="";}publicstringCity{get;init;}}在参数构造函数中初始化,它们也可以通过字段或属性初始化器初始化:并始终将结构成员设置为其默认值。有关结构中无参数构造函数的更多信息,请参见结构类型。02记录结构从C#10开始,现在可以使用记录结构定义记录。这些类似于C#9中引入的记录类:publicrecordstructPerson{publicstringFirstName{get;init;}publicstringLastName{get;init;}}你可以继续使用record来定义记录类,也可以使用记录类来明确。结构已经具有价值平等-当你比较它们时,它是按价值计算的。记录结构添加了IEquatable支持和==运算符。记录结构提供IEquatable的自定义实现以避免反射的性能问题,并且它们包括记录函数,例如ToString()覆盖。记录结构可以是位置的,主构造函数隐式声明公共成员:publicrecordstructPerson(stringFirstName,stringLastName);主构造函数的参数成为记录结构的公共自动实现属性。与记录类不同,隐式创建的属性是可读/可写的。这使得将元组转换为命名类型变得更加容易。将返回类型从类似(stringFirstName,stringLastName)的元组更改为Person的命名类型可以清理您的代码并保持成员名称一致。声明一个位置记录结构很容易并且维护可变语义。如果您声明一个与主构造函数参数同名的属性或字段,则不会合成任何自动属性,而是使用您的属性。要创建不可变记录结构,请将只读添加到该结构(就像您可以添加到任何结构一样)或将只读应用于各个属性。对象初始值设定项是构造阶段的一部分,在该阶段可以设置只读属性。这只是使用不可变记录结构的一种方式:varperson=newPerson{FirstName="Mads",LastName="Torgersen"};publicreadonlyrecordstructPerson{publicstringFirstName{get;init;}publicstringLastName{get;init;}}本文有关记录结构的更多信息。Record类https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record03Record类中ToString()的密封修饰符也得到了改进。从C#10开始,ToString()方法可以包含密封修饰符,这会阻止编译器为任何派生记录合成ToString实现。在本文的记录中了解有关ToString()的更多信息。有关ToString()的更多信息https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record#built-in-formatting-for-display04结构和匿名类型的表达式C#10Withexpressionsare支持所有结构,包括记录结构,以及匿名类型:varperson2=personwith{LastName="Kristensen"};这将返回一个具有新值的新实例。您可以更新任意数量的值。您未设置的值将与初始实例保持相同。在本文中了解有关with的更多信息https://docs.microsoft.com/dotnet/csharp/language-reference/builtin-types/record#built-in-formatting-for-displayMicrosoft向C#添加内插字符串时的内插字符串改进,微软一直觉得语法在性能和表现力上可以做得更多。01内插字符串处理程序今天,编译器将内插字符串转换为对string.Format的调用。这会导致大量分配——参数的装箱、参数数组的分配,当然还有生成的字符串本身。此外,它在实际插值的含义上没有任何回旋余地。在C#10中,Microsoft添加了一个库模式,允许API“接管”内插字符串参数表达式的处理。例如,考虑StringBuilder.Append:varsb=newStringBuilder();sb.Append($"你好{args[0]},你好吗?");到目前为止,这将调用附加到StringBuilder块的Append(string?value)重载。但是,Append现在有一个新的Append重载(refStringBuilder.AppendInterpolatedStringHandler处理程序),当使用内插字符串作为参数时,它优先于字符串重载。通常,当您看到SomethingInterpolatedStringHandler形式的参数类型时,API作者在幕后做了一些工作,以更恰当地处理内插字符串以达到他们的目的。在Microsoft的Append示例中,字符串“Hello”、args[0]和“,howareyou?”将单独附加到StringBuilder,这样效率更高并且产生相同的结果。有时您只想在特定条件下完成构建字符串的工作。一个例子是Debug.Assert:Debug.Assert(condition,$"{SomethingExpensiveHensHere()}");大多数情况下,条件为真,不使用第二个参数。但是,每次调用都会评估所有参数,这会不必要地减慢执行速度。Debug.Assert现在有一个带有自定义插值字符串生成器的重载,可确保除非条件为假,否则甚至不会评估第二个参数。最后,这是一个实际更改给定调用中字符串插值行为的示例:String.Create()允许您在孔中指定表达式,IFormatProvider用于格式化插值字符串参数本身:String.Create(CultureInfo.InvariantCulture,$"Theresultis{result}");您可以在本文和有关创建自定义处理程序的教程中了解有关内插字符串处理程序的更多信息。创建自定义处理程序https://docs.microsoft.com/dotnet/csharp/languagereference/tokens/interpolated#compilation-of-interpolated-strings有关内插字符串处理程序的更多信息https://docs.microsoft。com/dotnet/csharp/whats-new/tutorials/interpolated-string-handler02常量内插字符串如果内插字符串中的所有孔都是常量字符串,则生成的字符串现在也是常量。这样可以让你在更多的地方使用字符串插值语法,比如属性:[Obsolete($"Call{nameof(Discard)}instead")]注意空洞必须用常量字符串填充。不能使用其他类型,例如数字或日期值,因为它们是区域性敏感的并且不能在编译时求值。其他改进C#10为整个语言带来了许多较小的改进。其中一些只是让C#以您期望的方式工作。在解构中混合声明和变量在C#10之前,解构要求所有变量都是新的,或者所有变量都必须事先声明。在C#10中,您可以混合使用:intx2;inty2;(x2,y2)=(0,1);//WorksinC#9(varx,vary)=(0,1);//WorksinC#9(x2,vary3)=(0,1);//WorksinC#10onwards在关于解构的文章中了解更多信息。改进的明确赋值如果您使用未显式赋值的值,C#会引发错误。C#10可以更好地理解您的代码并产生更少的虚假错误。这些相同的改进还意味着您将看到更少的虚假错误和空引用警告。在C#10的新增功能一文中了解有关C#确定性分配的更多信息。What'snewinC#10articlehttps://docs.microsoft.com/dotnet/csharp/whats-new/csharp-10#improved-definite-assignment扩展属性模式C#10添加了扩展属性模式,以便更轻松地访问嵌套属性值在架构中。例如,如果微软在上面的Person记录中添加地址,微软可以通过以下两种方式进行模式匹配:};if(objisPerson{Address:{City:"Seattle"}})Console.WriteLine("Seattle");if(objisPerson{Address.City:"Seattle"})//ExtendedpropertypatternConsole.WriteLine("Seattle");扩展属性模式简化了代码并使其更易于阅读,尤其是在匹配多个属性时。在模式匹配文章中了解有关扩展属性模式的更多信息。模式匹配文章https://docs.microsoft.com/dotnet/csharp/languagereference/operators/patterns#property-pattern调用方表达式属性CallerArgumentExpressionAttribute提供有关方法调用上下文的信息。与其他CompilerServices属性一样,此属性适用于可选参数。在这种情况下,一个字符串:voidCheckExpression(boolcondition,[CallerArgumentExpression("condition")]string?message=null){Console.WriteLine($"Condition:{message}");}传递给CallerArgumentExpression的参数名称是名称的不同参数。作为参数传递给此参数的表达式将包含在字符串中。例如vara=6;varb=true;CheckExpression(true);CheckExpression(b);CheckExpression(a>5);//Output://Condition:true//Condition:b//Condition:a>5ArgumentNullException。ThrowIfNull()是一个很好的例子,说明如何使用这个属性。它通过默认提供值来避免必须传入参数名称:voidMyMethod(objectvalue){ArgumentNullException.ThrowIfNull(value);}/caller-information#argument-expressions