C# 二十年语法变迁之 C# 7参考

https://benbowen.blog/post/two_decades_of_csharp_iii/

自从 C# 于 2000 年推出以来,该语言的规模已经大大增加,我不确定任何人是否有可能在任何时候都对每一种语言特性都有深入的了解。因此,我想写一系列快速参考文章,总结自 C# 2.0 以来所有主要的新语言特性。我不会详细介绍它们中的任何一个,但我希望这个系列可以作为我自己(希望你也是!)的参考,我可以不时回过头来记住我使用的工具工具箱里有。

开始之前的一个小提示:我将跳过一些更基本的东西(例如 C# 2.0 引入了泛型,但它们的使用范围如此广泛,以至于它们不值得包括在内);而且我还可以将一些功能“粘合”在一起,以使其更简洁。本系列并不打算成为该语言的权威或历史记录。相反,它更像是可能派上用场的重要语言功能的“备忘单”。您可能会发现浏览左侧的目录以搜索您不认识或需要快速提醒的任何功能很有用。

C# 7.0

元组

元组是包含一组两个或多个相关对象的结构。它们对于处理或返回通常不会一起关联到类或结构中的多个相关值很有用。

  1. static void Test() {
  2. // This line creates a 3-tuple (a tuple with 3 items) named 'userDetails'.
  3. // The tuple's three properties are an int named Age, a string named Name, and a float named TestScore
  4. var userDetails = (Age: 30, Name: "Ben", TestScore: 50f);
  5. // ... Later on we can use the tuple like any old struct/class:
  6. var age = userDetails.Age;
  7. var name = userDetails.Name;
  8. var testScore = userDetails.TestScore;
  9. }

• “元组声明”

如果我们想将元组传递给另一个函数或将其存储在容器中怎么办?我们可以使用类似的语法声明一个元组类型:

  1. static readonly List<(int Age, string Name, float TestScore)> _userList;
  2. static void AddUserToList((int Age, string Name, float TestScore) user) {
  3. _userList.Add(user);
  4. }
  5. // ...
  6. static void Test() {
  7. AddUserToList((30, "Ben", 50f));
  8. }

• “元组类型声明”

元组的一个很好的用途是更适合替代方法上的“输出”参数:

  1. static (int Min, int Max) GetLimits(IReadOnlyCollection<int> values) {
  2. return (values.Min(), values.Max());
  3. }

• “元组返回类型”

  1. static void Test() {
  2. var values = new[] { 1, 2, 3, 4, 5, 6, 7 };
  3. // Using the GetLimits() function from previous example
  4. var (min, max) = GetLimits(values);
  5. Console.WriteLine($"Min value is {min}");
  6. Console.WriteLine($"Min value is {max}");
  7. }
  8. // Alternative syntax for pre-existing variables:
  9. static int _min;
  10. static int _max;
  11. static void Test() {
  12. var values = new[] { 1, 2, 3, 4, 5, 6, 7 };
  13. (_min, _max) = GetLimits(values); // No 'var' because we're not declaring new variables.
  14. Console.WriteLine($"Min value is {_min}");
  15. Console.WriteLine($"Min value is {_max}");
  16. }

• “元组解构”

但是请注意,元组不应该被过度使用。当你真正需要的是一个适当的类或结构时,到处使用元组是很危险的。将相关数据一起封装成“真实”类型!例如,实际上,这些示例中的字段应封装为User对象。我建议不要在公共 API 中使用元组(我只在方法/类的实现和私有帮助函数中使用它们)。

可以使用或不使用程序员定义的属性名称来声明元组(从 C# 7.1 开始):

  1. static void Test() {
  2. var age = 30;
  3. var name = "Ben";
  4. var testScore = 50f;
  5. var implicitlyNamedTuple = (age, name, testScore);
  6. var explicitlyNamedTuple = (Age: age, Name: name, TestScore: testScore);
  7. //var userAge = implicitlyNamedTuple.age; // Looks ugly! 'age' property is lower-case
  8. var userAge = explicitlyNamedTuple.Age; // Much better :)
  9. }

• “元组属性命名”

请注意,当自动创建名称时,编译器只需复制传入的参数、字段、本地或属性的名称来创建元组;因此我们的implicitlyNamedTuple的属性是小写的。正是出于这个原因,我总是喜欢使用显式命名(因为当然,C# 中的驼峰命名法对于公共成员来说是非常规的)。

任何元组的基础类型是ValueTuple<T1, T2, ..., Tn>,其中类型参数的数量等于元组中的项目数。注意不要使用Tuple<>代替ValueTuple<>;这是一种较旧的、大部分已弃用的类型,效率较低,与ValueTuple<>相比几乎没有任何优势。

元组成员的命名实际上是一个编译器技巧。ValueTuple<>中属性的“真实”名称始终是Item1、Item2、Item3等。在声明元组类型时,编译器会添加一个专门的属性在幕后让它工作,这也是智能感知的。

尽管元组是值类型,但有趣的是它们是可变的。这有一些性能影响,以及使用可变值类型时通常的“陷阱”。

自定义解构

您可以通过声明公共Deconstruct()方法 使任何类型像元组一样可解构。该方法必须返回 void,并且所有参数都必须是参数- 这些将成为将被分配的解构变量。这是一个例子:

  1. class User {
  2. public string Name { get; set; }
  3. public int Age { get; set; }
  4. public void Deconstruct(out string name, out int age) {
  5. name = Name;
  6. age = Age;
  7. }
  8. }
  9. // ...
  10. static void Test() {
  11. var user = new User { Name = "Ben", Age = 30 };
  12. var (name, age) = user;
  13. }

• “用户类解构器”

简单模式匹配

这是一系列旨在使编写某些程序更容易的各种新功能。

is 表达式

对于检查对象是否是给定类型的实例并同时为所述对象创建该类型的本地别名最有用:

  1. var user = GetUser();
  2. if (user is Manager manager) {
  3. Console.WriteLine($"User is a manager and has {manager.Reports.Count} direct reports!");
  4. }

• “类型模式的“是”表达式”

Null 值永远不会匹配is 表达式,因此is 表达式可用于过滤掉 null 值:

  1. var userList = GetUserList();
  2. if (userList.FirstOrDefault() is User user) {
  3. Console.WriteLine($"User database contains at least one user!");
  4. }

• “用于空检查的“是”表达式”

除了类型模式,还支持常量模式。这些都可以在 switch 表达式中使用。当与允许过滤匹配的when 表达式结合使用时,常量模式最有用。

当匹配多个 switch case 时,只输入遇到的第一个匹配 case(你甚至不能用goto case故意在 case 之间跳转)。一个例外是默认情况,它总是最后评估。

  1. var user = GetUser();
  2. // Type pattern matching
  3. switch (user) {
  4. case null:
  5. Console.WriteLine("No user found.");
  6. break;
  7. case Manager m when m.Department == Department.Engineering:
  8. Console.WriteLine($"User is a manager in the engineering department and has {m.Reports.Count} direct reports.");
  9. break;
  10. case Manager m when m.Department == Department.Marketing:
  11. Console.WriteLine($"User is a manager in the marketing department and manages {m.CustomerAccounts.Count} customer accounts.");
  12. break;
  13. case Trainee t when t.AgeInYears >= 18:
  14. Console.WriteLine($"User is a trainee and has completed {t.Courses.Count(c => c.IsCompleted)} of their training courses.");
  15. break;
  16. case Trainee t: // This case will only be entered if the one above was not
  17. Console.WriteLine($"User is a junior trainee.");
  18. break;
  19. default:
  20. Console.WriteLine($"User is just a user.");
  21. break;
  22. }

• “使用 When 表达式切换模式匹配”

与is expression类似,null 用户无法匹配首先检查其类型的任何大小写。即使我们将user声明为类型为Manager的局部变量,如果GetUser()返回null值,则永远不会输入 case case Manager m:(即使我们删除了case null:)。

局部函数

此功能允许在函数内声明函数。这些内部(即本地)函数只能在外部函数的范围内访问。

  1. static void PrintUserReport(List<User> users) {
  2. string CreateBioString(User u) {
  3. var bioStart = $"{u.Name}: {u.AgeInYears} years old, {(DateTime.Now - u.JoinDate).TotalYears:N1} years at company";
  4. if (u.AgeInYears <= 18) return bioStart;
  5. else return $"{bioStart}; marital status: {u.MaritalStatus}";
  6. }
  7. foreach (var user in users) {
  8. Console.WriteLine(CreateBioString(user));
  9. }
  10. }
  11. // ... On User.cs ...
  12. bool DueForPayRaise {
  13. get {
  14. bool IsEligible() {
  15. return AgeInYears >= 18 && (DateTime.Now - u.JoinDate).TotalYears >= 1d;
  16. }
  17. return IsEligible() && (DateTime.Now - u.LastPayRaise).TotalYears >= 1d;
  18. }
  19. }

• “本地函数”

内联“Out”变量声明

这个简单的特性允许在使用 out 变量时更加简洁:

  1. // BEFORE
  2. static void Test() {
  3. int parseResult;
  4. if (Int32.TryParse(someString, out parseResult)) {
  5. // ... Use parseResult here
  6. }
  7. }
  8. // AFTER
  9. static void Test() {
  10. if (Int32.TryParse(someString, out var parseResult)) {
  11. // ... Use parseResult here
  12. }
  13. }

• “内联输出变量”

抛出表达式

这个非常方便的功能也有助于简洁。它允许您在通常期望值的地方抛出异常。例子:

  1. User _currentUser;
  2. public User CurrentUser {
  3. get {
  4. return _currentUser;
  5. }
  6. set {
  7. _currentUser = value ?? throw new ArgumentNullException(nameof(value));
  8. }
  9. }

• “Throw Expressions Example A”

  1. public MaritalStatus MaritalStatus {
  2. get {
  3. return _currentUser.AgeInYears >= 18
  4. ? _currentUser.MaritalStatus
  5. : throw new InvalidOperationException($"Can not disclose marital status of non-adult.");
  6. }
  7. }

• “Throw Expressions Example B”

参考本地和返回

这个与性能相关的特性允许使用、存储和返回对变量/数据位置的引用。

从早期的 C# 开始,引用参数允许我们将变量的引用传递给方法。现在我们还可以返回对属性、字段或其他堆分配变量(例如数组值)的引用:

  1. int _viewMatricesStartIndex;
  2. int _projectionMatricesStartIndex;
  3. int _worldMatricesStartIndex;
  4. Matrix4x4[] _matrices;
  5. public ref Matrix4x4 GetMatrix(MatrixType type, int offset) {
  6. switch (type) {
  7. case MatrixType.ViewMatrix:
  8. return ref _matrices[_viewMatricesStartIndex + offset];
  9. case MatrixType.ProjectionMatrix:
  10. return ref _matrices[_projectionMatricesStartIndex + offset];
  11. case MatrixType.WorldMatrix:
  12. return ref _matrices[_worldMatricesStartIndex + offset];
  13. default:
  14. throw new ArgumentOutOfRangeException(nameof(type));
  15. }
  16. }

• “Ref Returns”

此方法返回对 _matrices 数组中 Matrix4x4 的引用,而不是其value 的副本。对于复制大型值类型实例将不可避免的情况,这可以带来性能优势。 在方法中使用返回的引用需要声明一个ref local:

  1. static void Test() {
  2. // Sets _matrices[_viewMatricesStartIndex + 3] to Matrix4x4.Identity
  3. ref var viewMatrix = ref GetMatrix(MatrixType.ViewMatrix, 3);
  4. viewMatrix = Matrix4x4.Identity;
  5. // We can dereference the reference and copy its value to a local here by using the standard local variable declaration syntax
  6. var projMatrix = GetMatrix(MatrixType.ProjectionMatrix, 2);
  7. projMatrix.M11 = 3f; // Changes only the local 'projMatrix', does not affect anything in _matrices
  8. }

• “Ref Locals”

这两种单独的语法允许“选择加入”在按引用返回的方法上使用 ref locals;当我们不想要或不需要它时,一直忽略它。

我们还可以通过直接从 ref-returning 方法返回的引用来设置值:

  1. static void Test() {
  2. // Sets _matrices[_viewMatricesStartIndex + 3] to Matrix4x4.Identity
  3. GetMatrix(MatrixType.ViewMatrix, 3) = Matrix4x4.Identity;
  4. }

• “通过返回的引用设置值”

弃元

此功能允许声明您忽略所需参数的意图。使用下划线 ( _ ) 表示您不想使用 out 参数、表达式的结果或 lambda 参数:

  1. static void Test() {
  2. // Just want to test if this is a valid value; we don't need the parsed value
  3. if (!Int32.TryParse(_userInput, out _)) {
  4. // ..
  5. }
  6. // Don't want the result of this method, just need to invoke it
  7. _ = _someInterface.SomeMethod();
  8. // Don't want to use these parameters in a lambda (C# 9+ only)
  9. _someInterface.DoThing((_, _, param3, _) => param3 == "hello");
  10. }

数字分隔符

此功能允许您使用下划线分隔整数文字的数字:

  1. const int DecimalConstant = 123_456_789;
  2. const int HexadecimalConstant = 0xAB_CD_EF;

• “数字分隔符”

二进制字面量

此功能允许以二进制格式声明整数常量:

  1. const int BinaryConstant = 0b1110_0011_1101_0001;

• “二进制文字”

C# 7.1

ValueTask/ValueTask 和 IValueTaskSource

在 C# 中 封装future的主要方法是使用Task和Task类。在大多数情况下,此范例运行良好,但在严格控制的性能场景中,Task / Task对象的持续创建会对垃圾收集器施加不必要的压力。

ValueTask和ValueTask是允许使用类似任务的语义(包括 async/await)但不总是创建引用类型的实例来跟踪异步操作的两种类型。

对于异步函数,您希望该函数成为热路径的一部分,频繁调用,并且该函数通常能够同步完成,ValueTask很有意义:

  1. // If we assume most users are not logged in, we can avoid allocating a Task object every time we invoke this method for most cases
  2. // In the case where the user IS logged in, we wrap an actual Task<int> which will then be deferred to
  3. public ValueTask<int> GetUserIDAsync(User u) {
  4. if (!u.IsLoggedIn) return ValueTask.FromResult(0);
  5. else return new ValueTask<int>(FetchUserIDFromDatabaseAsync(u)); // Assume FetchUserIDFromDatabaseAsync() returns a Task<int>
  6. }

• “ValueTask 示例”

可以像常规Task或Task一样等待 返回的ValueTask或ValueTask对象-但只能等待一次。

注意:C# 为声明公共GetAwaiter()方法(或具有通过扩展方法定义的方法)的任何类型提供await支持,该方法返回具有一小组先决条件公共成员的对象。ValueTask和ValueTask实现了这个接口。

注意:实际上,框架缓存了一些常见的 Task 结果

当方法可以同步完成时,这种方法可以消除不必要的垃圾。

ValueTask和ValueTask 都有可以采用IValueTaskSource/IValueTaskSource类型的对象的构造函数。这些类型允许您重用/池化对象来处理异步状态机和继续调用。这些类型只需要实现IValueTaskSource / IValueTaskSource。

实现方法有以下三种:

异步状态机将调用GetStatus以获取异步操作的当前状态。

异步状态机将调用GetResult以获取异步操作完成时的结果。

OnCompleted将由异步状态机调用,以将延续传递给您的实现,在异步操作完成时必须调用该延续;或者如果已经完成则立即调用。

如上所述,多次等待或从任何 ValueTask 获取结果是错误的;这允许我们假设GetResult每次操作只会被调用一次(超过这个次数是用户的错误,可以被认为是不受支持的)。同样,它还允许我们假设一旦调用GetResult,IValueTaskSource实例就可以重新用于下一个异步操作。

传递给所有方法的短令牌可用于确保遵守此条件。

默认文字

这个小功能允许在指定类型的默认值时省略类型名称:

  1. // Before
  2. const int Zero = default(int);
  3. // After
  4. const int Zero = default;

• “默认文字常量”

  1. public string GetUserName(User u) {
  2. // ...
  3. }
  4. // Before
  5. GetUserName(default(User)); // Passes null
  6. // After
  7. GetUserName(default); // Passes null

• “默认文字方法调用”

异步Main函数

此功能允许“一直向上”使用 async/await。它允许使Main()函数(应用程序的入口点)异步。

  1. public static async Task<int> Main(string[] args) {
  2. try {
  3. await Engine.Initialise();
  4. return 0;
  5. }
  6. catch (Exception e) {
  7. Log.Error(e);
  8. return 1;
  9. }
  10. }

• “Async Main”

C# 7.2

在参数中,只读结构,只读引用返回

继 ref locals 和 return 之后,此功能添加了一些更多功能来传递对结构的引用。这些功能主要是为性能敏感的场景提供的。只读结构是其字段永远不能修改的结构(即它是不可变的):

  1. readonly struct BigStruct {
  2. public readonly int Alpha;
  3. public readonly float Bravo;
  4. public readonly int Charlie;
  5. public readonly float Delta;
  6. }

• “只读结构”

除了帮助您保持不变性外,将结构声明为只读还有助于编译器在使用in参数时避免防御性副本。in参数与 ref 参数一样,是通过引用传递的参数。然而,另外,in参数是只读的:

  1. void Test(in Matrix4x4 viewMatrix) {
  2. viewMatrix.M11 = 123f; // Won't compile even though Matrix4x4 is a mutable struct, 'in' parameters are readonly
  3. }

•In 参数

尽管编译器尽一切努力防止直接修改通过引用传入的结构;并不总是可以保证不进行任何修改。因此,为了确保正确性,编译器必须在某些情况下对参数进行防御性复制,除非结构类型本身被标记为readonly。

因为in参数是一种性能特性,所以将它们与非只读结构一起使用几乎总是一个坏主意。有关详细信息,请参阅MSDN 上的避免将可变结构作为 In 参数

当调用带有in参数的方法时,in调用站点的说明符是可选的。但是,指定它有两个用途:

  1. // First case: Explicitly invoking an overloaded method that takes an [c]in[/c] parameter:
  2. static void PrintFirstElement(Matrix4x4 m) => Console.WriteLine(m.M11);
  3. static void PrintFirstElement(in Matrix4x4 m) => Console.WriteLine(m.M11);
  4. static void Test() {
  5. var m = GetMatrix();
  6. PrintFirstElement(m); // Invokes first method, passes 'm' by value (i.e. copied)
  7. PrintFirstElement(in m); // Invokes second method, passes 'm' by readonly reference
  8. }

• “在方法重载调用时”

  1. // Second case: Forcing the passing of an 'in' parameter to be a reference to live variable
  2. static void PrintFirstElement(in Matrix4x4 m) => Console.WriteLine(m.M11);
  3. static void Test() {
  4. // Matrix4x4.Identity is a static property that returns a new Matrix4x4
  5. PrintFirstElement(Matrix4x4.Identity); // Compiles, because the compiler creates a temporary variable on the stack that is what is referred to
  6. PrintFirstElement(in Matrix4x4.Identity); // Fails, because we're creating a reference to something that only exists as a temporary variable
  7. }

•“在调用中以显式通过引用”

最后,只读引用返回允许返回对不允许修改它所引用的变量的变量的引用。要使用这样的引用(而不是获取返回引用的副本),局部变量也必须声明为ref readonly:

  1. static Matrix4x4 _viewMat;
  2. static ref readonly Matrix4x4 GetMatrix() => ref _viewMat;
  3. static void Test() {
  4. ref readonly var mat = ref GetMatrix();
  5. var matCopy = mat;
  6. mat.M11 = 3f; // This line won't compile, we can not modify a readonly ref
  7. matCopy.M11 = 3f; // This line is fine, 'matCopy' is a local stack copy of the variable pointed to by 'mat'
  8. }

• “只读 Ref Returns and Locals”

Ref struct、Span、Memory

Ref struct是一种新的结构类型(即值类型),包含“内部指针”;即对对象的数据或偏移量的引用(与对对象本身的引用相反)。ref struct的实例只能存在于堆栈中;因此对它们的使用方式有一些限制(参见下面的第二个示例)。ref struct最突出的用法是Span类型。跨度是对包含 0 个或多个相同类型元素的连续内存块的引用。声明和存储此内存的方式无关紧要 - Span始终可以指向数据。

  1. static char[] _charArray = { 'A', 'l', 'p', 'h', 'a' };
  2. static List<char> _charList = new List<char> { 'T', 'a', 'u' };
  3. static void PrintCharSpanData(ReadOnlySpan<char> charSpan) {
  4. Console.Write($"Given span is {charSpan.Length} characters long: ");
  5. Console.WriteLine($"\"{new String(charSpan)}\"");
  6. }
  7. unsafe static void Test() {
  8. var heapArraySpan = _charArray.AsSpan();
  9. var listSpan = CollectionsMarshal.AsSpan(_charList);
  10. Span<char> stackArraySpan = stackalloc char[] { 'O', 'm', 'e', 'g', 'a' };
  11. const string UnmanagedDataString = "Epsilon";
  12. var numBytesToAlloc = sizeof(char) UnmanagedDataString.Length;
  13. var pointerSpan = new Span<char>((void) Marshal.AllocHGlobal(numBytesToAlloc), UnmanagedDataString.Length);
  14. UnmanagedDataString.AsSpan().CopyTo(pointerSpan);
  15. var singleCharOnStack = 'O';
  16. var stackSpan = new Span<char>(&singleCharOnStack, 1);
  17. var stringSpan = "Delta".AsSpan();
  18. // =======
  19. PrintCharSpanData(heapArraySpan); // Given span is 5 characters long: "Alpha"
  20. PrintCharSpanData(listSpan); // Given span is 3 characters long: "Tau"
  21. PrintCharSpanData(stackArraySpan); // Given span is 5 characters long: "Omega"
  22. PrintCharSpanData(pointerSpan); // Given span is 7 characters long: "Epsilon"
  23. PrintCharSpanData(stackSpan); // Given span is 1 characters long: "O"
  24. PrintCharSpanData(stringSpan); // Given span is 5 characters long: "Delta"
  25. }

• “Span 声明和使用”

上面的示例演示了创建char跨度的六种不同方法。但无论如何创建Span,它都可以以相同的方式使用:作为连续的字符范围。

ReadOnlySpan是另一种类型,顾名思义,它是一个Span,但不允许修改它指向的数据。Span可隐式转换为ReadOnlySpan(假设类型参数T相同);这允许我们将Span传递给PrintCharSpanData(),即使该方法采用ReadOnlySpan。

上面的代码仅作为创建和使用Span / ReadOnlySpan的示例。有些操作是“不安全的”或在使用时需要小心。特别注意,手动分配的内存(使用AllocHGlobal)应该再次释放,并且在访问支持列表的数组时(通过CollectionsMarshal ),重要的是在相关Span的使用完成之前不修改列表.

因为Span、ReadOnlySpan和任何其他ref struct不得转义堆栈(或内部引用可能无效),因此对其类型的变量有使用限制:

  1. // Invalid: Ref struct types can not be the element type of an array
  2. // Because arrays are stored on the heap
  3. static readonly Span<int>[] _intSpanArray;
  4. // Invalid: Ref struct types can not be fields or properties of any class or struct except ref structs
  5. // Because class instances are stored on the heap, and struct instances may be boxed (i.e. a copy stored on the heap)
  6. public Span<int> SomeSpan { get; set; }
  7. // Invalid: Ref struct types can not implement interfaces
  8. // Because using them as their interface type would always require boxing
  9. readonly ref struct MyRefStruct : IEquatable<MyRefStruct> { }
  10. // Invalid: Ref struct types can not be cast to object (or boxed in any way)
  11. // Because boxed copies of structs are stored on the heap
  12. var boxedSpan = (object) mySpan;
  13. // Invalid: Ref struct types can not be type arguments
  14. // Because usage of elements can not currently be verified as valid (and some usages will never be valid, i.e. List<T>)
  15. var list = new List<Span<int>>();
  16. // Invalid: Ref struct types can not be closed-over (captured) by a lambda/anonymous function
  17. // Because captured variables must be stored in a heap object so that they're still available when the lambda is executed
  18. var filtered = someEnumerable.Where(x => x[0] == mySpan[0]);
  19. // Invalid: Ref struct types can not be used in an async method (locals or parameters)
  20. // Because locals in async methods may be stored in heap objects to become part of the internal state machine built by the compiler
  21. async Task SomeMethodAsync(Span<int> mySpan) { / ... / }

• “Ref Struct 使用限制”

由于这些限制,提供了另一种称为Memory的 非引用结构类型。Memory必须仅封装托管堆分配的、GC 跟踪的内存。

  1. static char[] _charArray = { 'A', 'l', 'p', 'h', 'a' };
  2. static List<char> _charList = new List<char> { 'T', 'a', 'u' };
  3. unsafe static void Test() {
  4. // Create a Memory<T> that wraps a new array copy of the data,
  5. // rather than pointing to the actual list data directly like we did with the Span<T> example:
  6. var charListAsMemory = _charList.ToArray().AsMemory();
  7. // Alternatively, create a Memory<T> that encapsulates just part of an existing array
  8. // (this can also be done with Span<T>)
  9. var charArraySubstringAsMemory = new Memory<char>(_charArray, 1, 3);
  10. PrintCharMemoryData(charListAsMemory); // Given memory is 3 characters long: "Tau"
  11. PrintCharMemoryData(charArraySubstringAsMemory); // Given memory is 3 characters long: "lph"
  12. }
  13. static void PrintCharMemoryData(ReadOnlyMemory<char> charMemory) {
  14. Console.Write($"Given memory is {charMemory.Length} characters long: ");
  15. Console.WriteLine($"\"{new String(charMemory.Span)}\""); // Can use the .Span property to create a Span<T> when required
  16. }

• “Memory 实例化和使用示例”

Span和Memory的 使用指南非常广泛,但如果编写使用它们的 API,则应阅读这些指南。Microsoft 在此处有一个专门讨论此主题的页面:Memory 和 Span 使用指南

私有保护访问修饰符

private protected访问修饰符 将成员的可见性限制为仅在同一程序集中的派生类,而不是预先存在的受保护的内部访问修饰符将可见性限制为仅派生类或同一程序集中的类。

C# 7.3

枚举、委托和非托管通用约束

枚举约束允许指定类型参数类型必须是枚举:

  1. // 'Enum' constraint lets us ensure that T is an enum type
  2. // 'struct' constraint is optional but lets us make 'valueToHighlight' nullable
  3. static void PrintAllEnumNames<T>(T? valueToHighlight) where T : struct, Enum {
  4. foreach (var value in (T[]) Enum.GetValues(typeof(T))) {
  5. if (value.Equals(valueToHighlight)) Console.WriteLine($"{value} <----");
  6. else Console.WriteLine(value.ToString());
  7. }
  8. }

• “枚举约束”

同样,委托约束允许指定类型参数类型必须是委托:

  1. // Example from MSDN: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters#delegate-constraints
  2. public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
  3. where TDelegate : System.Delegate
  4. => Delegate.Combine(source, target) as TDelegate;

• “委托约束”

非托管泛型约束允许指定类型参数类型必须适合直接/基于指针的操作和/或“blittable”。使用此约束允许您将指针和其他“不安全”构造与您的通用类型变量一起使用:

  1. // This method copies a T reference to a T value via pointer
  2. static unsafe void Copy<T>(ref T src, T dest) where T : unmanaged => dest = src;
  3. static unsafe void Test() {
  4. int dest = 0;
  5. int src = 3;
  6. Copy(ref src, &dest);
  7. Console.WriteLine(dest); // Prints '3'
  8. }

• “非托管约束”

Stackalloc 初始化器

这些允许通过内联初始化程序初始化堆栈分配的内存:

  1. var intArray = stackalloc[] { 1, 2, 3 };

•“堆栈分配的 int 数组的初始化”

C# 二十年语法变迁之 C# 7参考的更多相关文章

  1. 简体中国版文档的Markdown语法

    Markdown文件 注意︰这是简体中国版文档的Markdown语法.如果你正在寻找英语版文档.请参阅Markdown︰ Markdown: Syntax. Markdown: Syntax 概述 哲 ...

  2. tn文本分析语言(二) 基本语法

    tn是desert和tan共同开发的一种用于匹配,转写和抽取文本的语言.解释器使用Python实现,代码不超过1000行. 本文主要介绍tn的基本语法.高级内容可以参考其他篇章.使用这样的语法,是为了 ...

  3. Markdown 语法整理

    Markdown 语法整理 白宁超 2015年7月24日14:57:49 一.字体设置 A First Level Header == A Second Level Header -- # 标题 ## ...

  4. Markdown语法说明(详解版)

    ####date: 2016-05-26 20:38:58 tags: Markdown tags && Syntax ##Markdown语法说明(详解版)杨帆发表于 2011-11 ...

  5. wireshark过滤语法总结

    抓包采用wireshark,提取特征时,要对session进行过滤,找到关键的stream,这里总结了wireshark过滤的基本语法,供自己以后参考.(脑子记不住东西) wireshark进行过滤时 ...

  6. wireshark过滤语法总结-重点偏移过滤

    http://chenjiji.com/post/3371.html 作者: CHAN | 发布: 2013 年 10 月 24 日 做应用识别这一块经常要对应用产生的数据流量进行分析. 抓包采用wi ...

  7. Markdown 简明语法手册

    Markdown 简明语法手册 本文原文http://www.jianshu.com/p/fdb5cbdaf244 根据个人使用情况有所修改. Markdown是一种轻量级标记语言,简称md.创始人为 ...

  8. markdown语法学习源码

    __注: 结合markdown官方文档 其中大部分例子和说明文字都摘自官方文档__官方链接:[Markdown: Basics (快速入门)](http://wowubuntu.com/markdow ...

  9. markdown语法学习效果预览

    注: 结合markdown官方文档 其中大部分例子和说明文字都摘自官方文档 官方链接:Markdown: Basics (快速入门). 一 段落.标题.区块代码 Markdown 支持两种标题的语法, ...

  10. 【nginx运维基础(6)】Nginx的Rewrite语法详解

    概述 重写URL是非常有用的一个功能,因为它可以让你提高搜索引擎阅读和索引你的网站的能力:而且在你改变了自己的网站结构后,无需要求用户修改他们的书签,无需其他网站修改它们的友情链接:它还可以提高你的网 ...

随机推荐

  1. 在Python中使用Process创建子进程遇到的问题

    假如使用Process创建子进程,那么在最后的函数调用时需要加上if __name__ == "__main__":语句,否则会报错. 未使用该语句 代码示例 from multi ...

  2. [python]使用标准库logging实现多进程安全的日志模块

    前言 原本应用的日志是全部输出到os的stdout,也就是控制台输出.因其它团队要求也要保留日志文件,便于他们用其他工具统一采集,另一方面还要保留控制台输出,便于出问题的时候自己直接看pod日志.具体 ...

  3. 【Gerrit】操作技巧

    多笔提交依赖 1. cherry-pick 同步的多笔代码前后有依赖,如第M笔提交是基于第N笔修改的,直接同步过去会有冲突,所以同步M笔提交时需要基于N笔提交,即写入第N笔commit-id 上述填的 ...

  4. [转帖]小米Redis的K8s容器化部署实践

    https://juejin.cn/post/6844904196924276743     背景 Why K8S How K8s Why Proxy Proxy带来的问题 K8s带来的好处 遇到的问 ...

  5. [转帖]在Linux中切换cmake版本

    在Linux中切换cmake版本https://blog.whsir.com/post-6804.html   在Linux系统中,有时需要使用cmake进行程序编译,由于不同的Linux系统导致安装 ...

  6. [转帖]金仓数据库KingbaseES误删除系统超级用户(superuser)权限的恢复方式

    https://blog.csdn.net/arthemis_14/article/details/129879269 在使用KingbaseES数据库的时候,系统默认存在一个跟系统初始化用户同名的S ...

  7. 自己想的一些判断存储长度的sql

    create table zhaobsh (t1 date ,t2 TIMESTAMP) insert into zhaobsh values (CURRENT_DATE,CURRENT_TIMEST ...

  8. 记一次 .NET某工控自动化系统 崩溃分析

    一:背景 1. 讲故事 前些天微信上有位朋友找到我,说他的程序偶发崩溃,分析了个把星期也没找到问题,耗费了不少人力物力,让我能不能帮他看一下,给我申请了经费,哈哈,遇到这样的朋友就是爽快,刚好周二晚上 ...

  9. 【小测试】fastcgo 调用 c 代码

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 找到了一个项目 https://github.com/pe ...

  10. 【验证码逆向专栏】某度滑块、点选、旋转验证码 v1、v2 逆向分析

    声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容.敏感网址.数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许 ...