范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

C二十年语法变迁之C8参考

  C# 二十年语法变迁之 C# 8参考
  自从 C# 于 2000 年推出以来,该语言的规模已经大大增加,我不确定任何人是否有可能在任何时候都对每一种语言特性都有深入的了解。因此,我想写一系列快速参考文章,总结自 C# 2.0 以来所有主要的新语言特性。我不会详细介绍它们中的任何一个,但我希望这个系列可以作为我自己(希望你也是!)的参考,我可以不时回过头来记住我使用的工具工具箱里有。:)
  开始之前的一个小提示:我将跳过一些更基本的东西(例如 C# 2.0 引入了泛型,但它们的使用范围如此广泛,以至于它们不值得包括在内);而且我还可以将一些功能"粘合"在一起,以使其更简洁。本系列并不打算成为该语言的权威或历史记录。相反,它更像是可能派上用场的重要语言功能的"备忘单"。您可能会发现浏览左侧的目录以搜索您不认识或需要快速提醒的任何功能很有用。
  C# 8.0可空引用类型
  此功能是对 C# 的一个重要补充,旨在通过添加编译时正确性检查来帮助防止运行时出现空引用异常。
  启用功能
  要在项目中启用可空检查,请在 .csproj 文件中的目标框架声明下方添加  Exe net5.0 enable 
  •"在项目文件中启用可空检查" 或者,对于您想要慢慢转换为可空检查的现有代码库,编译时指令可以在源代码中启用/禁用此功能:#able enable// able references enabled here
  #able disable// able references disabled here
  #able restore// Resets status to project settings (i.e. disabled unless enable is specified in csproj, in which case enabled)
  // See also https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/preprocessor-able
  • "可空指令"
  如果您在项目范围内关闭了可空引用,建议使用#able enable和#able restore(而不是#able disable),这样当您在项目范围内启用它时,您不会在某些地方意外禁用它。
  基本用法
  程序员可以将每个字段、参数或属性(引用类型,即类)划分为可空或不可空。未使用问号语法(即字符串?)声明的所有字段/属性/参数都被视为non-able。这些成员不应该设置空值。
  如果编译器检测到不可为空的字段/属性/参数可能具有分配给它的空值,则编译器将发出警告。
  如果编译器检测到您尝试取消引用可空字段/属性/参数而不确保其值不为空,编译器将发出警告。 以下示例显示了一个包含可空和不可空字段和属性的类,以及采用可空和不可空参数的方法:#able enableclass TestClass { // If we don"t assign a value to _nonableField here or in the constructor, the compiler will warn us string _nonableField;
  // We don"t have to supply any initial value for a able field, the default value of is acceptable string? _ableField;
  // If we don"t assign a value to NonableProperty here or in the constructor, the compiler will warn us public string NonableProperty { get; set; } = "Hello";
  // We don"t have to supply any initial value for a able property, the default value of is acceptable public string? ableProperty { get; set; }
  public TestClass(string? initialFieldValue) { // If we just assign initialFieldValue to _nonableField without the -coalesced fallback value, the compiler will warn us _nonableField = initialFieldValue ?? "Hi"; }
  public void PrintStringLengths(string nonableParameter, string? ableParameter) { Console.WriteLine($"Non-able parameter length is: {nonableParameter.Length}");
  // If we don"t use the -propagation operator (?.) or check ableParameter for first, the compiler will warn us Console.WriteLine($"able parameter length is: {ableParameter?.Length.ToString ?? "<>"}"); }}
  • "可空和不可空字段、属性和参数" 总之:
  如果我们在构造函数返回之前没有给_nonableField和NonableProperty赋值非空值,编译器会警告我们;
  如果我们在构造函数中将initialFieldValue分配给_nonableField时没有提供非空替代值,编译器会警告我们;
  如果我们尝试访问字符串的Length属性,编译器会警告我们吗?参数而不检查是否为空。 容错运算符 ( ! ) 请求编译器忽略潜在的空值 :public void PrintFieldLengthsIfNon { if (_ableField != ) PrintFieldLengths;}
  void PrintFieldLengths { Console.WriteLine($"Non-able field length is: {_nonableField.Length}");
  // -forgiving operator (!) disables compiler warning here caused by accessing .Length property of potentially- _ableField without check Console.WriteLine($"able field length is: {_ableField!.Length}");}
  • "-forgiving Operator"
  在您希望将作为不可为空引用的值的地方(例如,您知道该值将始终通过其他方式设置,或者您将 传递给对类进行单元测试等),您可以使用带有或默认文字(即!或default!)的 -forgiving 运算符:// We know this property will have a non- value set before it is used no matter what so// we can tell the compiler to not worry about it being here:public string Name { get; } = !;
  • "-Forgiven Property"
  泛型
  无约束和无注释的泛型方法仅使用调用站点中所述的给定类型参数:static T ReturnInput(T t) => t;
  var x = ReturnInput; // Emits a warning: Passing a reference to an input of type "string"var y = ReturnInput; // No warning, passing to an input of type "int?" is finevar z = ReturnInput((object?) ); // No warning, passing to an input of type "object?" is fine
  • "无注释的通用方法"
  请注意,如果我们从ReturnInput返回默认值,编译器仍会发出警告,即使它没有空注释。这是因为如果T不可为空但属于引用类型(即string),则返回default将返回无效的值。 要在泛型上下文中使用可空值,泛型类型应被约束为struct或class:static T? ReturnInput(T? t) where T : struct => t;
  var p = ReturnInput("test"); // Doesn"t compile, "string" isn"t a structvar q = ReturnInput(3); // Doesn"t compile, "int" is a struct but the compiler can"t make the leap that you want to  // implictly convert an object of type "int" to "int?", and that therefore T should be "int"  // (add explicit type parameter indication in angle brackets to fix)var r = ReturnInput; // Compiles, T is "int" and the input parameter is default(able)
  全屏查看代码[1]• "使用带有可为空引用的 "struct" 约束:"static T? ReturnInput(T? t) where T : class => t;
  var x = ReturnInput; // Compiles absolutely fine, T is "string" and therefore the parameter type is "string?"var y = ReturnInput; // Does not compile: "int?" is not a reference type (it"s an alias for able)var z = ReturnInput((object?) ); // Compiles absolutely fine, T is inferred to be "object"
  全屏查看代码[2]• "使用带有可空引用的‘类’约束:" 当约束到接口/子类型时,也可以指示类型参数可能是可为空的类型:static T? ReturnInput(T? t) where T : IComparable? => t; // Notice able token (?) after IComparable constraint
  var p = ReturnInput("test"); // No warning. Compiler would warn us if the constraint was "IComparable" without the able token (?)var q = ReturnInput(3); // No warning. Int32 implements IComparable, so this is fine.  // The able token (?) on the constraint indicates that the type MAY be able, not that it MUST bevar r = ReturnInput(3); // Doesn"t compile. able has never been able to satisfy interface constraints
  全屏查看代码[3]• "使用可为空的接口约束" 目前,没有约束或任何其他方式来编写接受任何可空类型(即可空引用类型和可空结构)的泛型方法。相反,新的not约束可用于禁止可为空的类型:static T? ReturnInput(T? t) where T : not => t; // Notice able token (?) after IComparable constraint
  var x = ReturnInput("test"); // Emits a warning, "string?" invalidates the not constraint for Tvar y = ReturnInput((int?) 3); // Emits a warning, "int?" invalidates the not constraint for Tvar z = ReturnInput((object?) ); // No warning. T is implictly set to "object" rather than "object?".  // Notice that we can still accept maybe- parameters and return maybe- values,  // the "not" constraint applies to the type parameter itself, not method parameters // or return values.
  全屏查看代码[4]• "使用非空约束"
  属性
  System.Diagnostics.CodeAnalysis命名空间提供了一些新属性,可应用于您自己的 API/代码中的各种代码元素,以帮助编译器确定 正确性 :// Allow indicates that a value is permitted when setting/passing a value.
  // Although the type of this property is string (and not string?) we will allow people to "set" a value,// which will actually be replaced with a non- fallback value. Hence we [Allow].
  [Allow] public string NonableProperty { get => _nonableField; set => _nonableField = value ?? "fallback";}
  • "-Correctness Assisting Attributes: Allow"// Disallow indicates that a value is not permitted when setting/passing a value.
  // Although the type of this property is string? we don"t wish to allow anyone to actually *set* a value,// it"s just that the value may be still be when retrieved. Hence we mark it with [Disallow].
  [Disallow]public string? ableProperty { get => _ableField; set => _ableField = value ?? throw new ArgumentException(nameof(value));}
  • "-Correctness Assisting Attributes: Disallow"// Maybe applied to a return element indicates that the returned value may be .// This is useful when working with generics, as "T?" isn"t always valid.
  [return: Maybe]public T GetElementByName(string name) { /* ... */ }
  • "-Correctness Assisting Attributes: Maybe"// Not indicates that a ref or out parameter, that is marked as able, will never be set as after the method returns.
  public void AppendHello([Not] ref string? a) {  if (a == ) a = "Hello"; else a = a + "Hello";}
  • "-Correctness Assisting Attributes: Not"// NotWhen indicates that a parameter is not when the return value of a method is true or false.
  // The annotation here indicates that when "TryParseUser" returns true, "u" will always have a value assigned.
  public bool TryParseUser(string userIdentifier, [NotWhen(true)] out User u) { /* ... */ }
  • "-Correctness Assisting Attributes: NotWhen" 还有一些适用于罕见用例的附加属性,都可以在这里找到:保留属性有助于编译器的空状态静态分析[5]。
  隐式类型变量
  任何用var 声明的局部变量总是被声明为可为空的,即使右边的表达式没有计算为可为空的类型:
  [6]原因在语言设计会议的笔记中[7]给出:
  "在这一点上,我们已经看到大量代码需要人们拼出类型而不是使用 var,因为代码可能稍后会分配 。" 不过,别担心。即使该类型被标记为可为空,编译器仍使用流分析来确定该值是否实际上可以为空。假设您分配的值不可为空,这意味着您仍然可以将隐式类型变量传递给期望不可为空引用的方法,并在没有警告的情况下取消引用该变量;直到/除非您为该变量分配一个新的可为空值。
  在某种意义上,在可为空的上下文中使用var创建的局部变量可以被认为处于"可以分配一个可为空的值,但编译器正在跟踪实际的空状态"的状态。因此,我个人喜欢将var -declared locals 视为混合的"可跟踪可空"类型。
  覆盖/实现方法
  C# 编译器知道覆盖/实现方法的上下文中的可空性。它考虑了协变和逆变;这意味着您可以删除返回类型的可空性并在输入上添加可空性,但不能相反:public interface ITest { public string? GetStr; public void SetStr(string? s);
  public string GetStrNon; public void SetStrNon(string s);}
  public class Test : ITest { public string GetStr => "Hello"; // Fine! public void SetStr(string s) { } // Warning here because you can not remove ability on an input (i.e. parameter)
  public string? GetStrNon => ; // Warning here because you can not add ability on an output (i.e. return value) public void SetStrNon(string? s) { } // Fine!}
  • "覆盖/实现可空性"
  易错性
  不幸的是,仍然有可能在没有任何警告的情况下创建可能出现空引用异常的情况:var stringArray = new string[10];Console.WriteLine(stringArray[0].Length); // NRE here, no warning!
  • "使用数组的可空上下文中的简单空引用异常" 即使我们的数组类型是字符串(而不是字符串?),编译器也无法强制我们用非空值初始化数组中的每个元素。因此,第二行的取消引用通过了"空测试"(因为表达式stringArray[0]返回的类型是不可为空的类型),所以没有发出警告,但我们最终得到一个空引用无论如何在运行时异常。
  使用结构可以看到类似的效果:readonly struct TestStruct { public readonly string S; public TestStruct(string s) => S = s;}
  sealed class TestClass { TestStruct _ts;
  public void SetTS(TestStruct ts) => _ts = ts;
  public void PrintSLength { Console.WriteLine(_ts.S.Length); // NRE here if _ts hasn"t been assigned a value yet }}
  • "使用结构的可空上下文中的简单空引用异常" 因为任何结构的默认值对于每个字段都是简单的零,所以任何引用类型字段都将设置为 。因此,在有人调用SetTS之前,_ts将等于default(TestStruct),这意味着_ts.S将为。
  和以前一样,因为_ts.S返回的是字符串而不是字符串?,编译器不会发出取消引用的警告,我们最终会在运行时出现空引用异常。
  默认接口实现
  此功能允许为接口方法指定默认实现:interface IExampleInterface { int GetAlpha;
  int GetBravo => 456;}
  class ExampleClass : IExampleInterface { public int GetAlpha => 123;}
  class Program { static void Main { IExampleInterface e = new ExampleClass;
  Console.WriteLine(e.GetAlpha); // Prints "123" Console.WriteLine(e.GetBravo); // Prints "456" }}
  • "基本 DIM 示例" 即使ExampleClass没有实现IExampleInterface.GetBravo,因为指定了默认实现,我们仍然可以调用e.GetBravo。
  此功能主要旨在帮助库/API 维护人员向现有接口添加新方法,而不会有破坏下游实现接口的现有类的风险。如果您有成百上千个实现一个接口的类,那么在不使用 DIM 的情况下更改该接口可能会变得非常昂贵。
  一些人担心这个特性会"破坏"接口的目的(即"接口是一种契约,不应该有实现")。但是,接口的"含义"并没有改变:它仍然是一种用于前向声明一组方法的机制,类型必须实现这些方法以支持功能的一个方面。唯一的区别是,现在在可以提供合理的默认实现的情况下,我们可以提供该默认。 请注意,默认实现仅作为显式实现[8]导入,因此不能用作实现类的常规公共方法:ExampleClass e = new ExampleClass; // Note "e" is now of type ExampleClass rather than IExampleInterface
  Console.WriteLine(e.GetAlpha);Console.WriteLine(e.GetBravo); // This line does not compile. We must cast "e" to type IExampleInterface to use this method.
  •"DIM 显式实现示例" 不幸的是,在撰写本文时,还没有官方支持的方法可以从覆盖实现中调用方法的默认实现(很像类继承的base.Method())。它是计划好的,但最终在发布前放弃了[9]。
  但是,如果您希望在实现类可访问的接口中创建默认实现,则可以将其移入受保护的静态或公共静态方法。接口现在可以声明静态成员(方法/属性和字段)。就像类或结构上的静态成员一样,这些成员在通过接口名称本身而不是通过实例调用时是可访问的:interface IExampleInterface { static readonly object _staticMutationLock = new object; static double _curValueMultiplier = 1d;
  public static double CurValueMultiplier { get { lock (_staticMutationLock) return _curValueMultiplier; } set { lock (_staticMutationLock) _curValueMultiplier = value; } }
  int CurValue { get; }
  void PrintCurValue => PrintCurValueDefaultImpl(this);
  protected static void PrintCurValueDefaultImpl(IExampleInterface @this) => Console.WriteLine(@this.CurValue * CurValueMultiplier);}
  class ExampleClass : IExampleInterface { public int CurValue { get; } = 100;
  public void PrintCurValue => IExampleInterface.PrintCurValueDefaultImpl(this); // Deliberately defer to default implementation}
  // ...
  class Program { static void Main { IExampleInterface e = new ExampleClass;
  IExampleInterface.CurValueMultiplier = 2d; // Static property access e.PrintCurValue; // Prints 200 }}
  • "静态接口方法"
  接口成员可见性和多态行为
  默认情况下,在接口上声明的成员始终是public。然而,现在可以将接口成员(静态或实例)声明为private、protected、internal或public(还有private protected和protected internal,我不会在此详述)。
  私有成员仅对声明它们的接口中的其他成员可见。
  内部成员对同一程序集中的任何其他源都是可见的。
  公共成员对任何其他来源都是可见的。 不幸的是,受保护的接口成员更复杂:
  受保护的 实例成员只能由子接口(而不是类)访问。
  受保护的 静态成员可以被子接口和实现类访问。
  外部代码根本无法访问受保护的成员,即使它们在实现类中被覆盖(为此,该类必须显式实现接口成员)。
  当一个类覆盖或提供受保护成员的实现时,仍然应用多态/虚拟化。这意味着当受保护的成员被调用时,仍然使用类的实现:interface IExampleInterface { protected void Test => Console.WriteLine("Interface");
  void InvokeTest => Test;}
  class ExampleClass : IExampleInterface { void IExampleInterface.Test => Console.WriteLine("Class"); // This MUST be implemented explicitly}
  class Program { static void Main { IExampleInterface e = new ExampleClass;
  e.InvokeTest; // Prints "Class" }}
  • "受保护的接口方法多态性" 当一个类实现了两个接口,它们都为同一个父接口成员提供默认实现时,实现类必须提供它自己的实现:interface IBase { char GetValue;}
  interface IDerivedAlpha : IBase { char IBase.GetValue => "A";}
  interface IDerivedBravo : IBase { char IBase.GetValue => "B";}
  class ExampleClass : IDerivedAlpha, IDerivedBravo { public char GetValue => "C"; // We must provide an implementation here or the compiler will emit an error}
  class Program { static void Main { var e = new ExampleClass;
  Console.WriteLine(e.GetValue); // Prints "C" Console.WriteLine(((IDerivedAlpha) e).GetValue); // Prints "C" Console.WriteLine(((IDerivedBravo) e).GetValue); // Prints "C" }}
  • "DIM Diamond 问题解决方案" 子接口(即从其他接口扩展而来的接口)可以为其父级成员提供默认实现;以及覆盖现有的默认实现,甚至将成员重新声明为抽象:interface IExampleInterface { int GetValue;}
  interface IExampleInterfaceChild : IExampleInterface { int IExampleInterface.GetValue => 123; // Provides a default implementation for GetValue in parent interface "IExampleInterface"}
  interface IExampleInterfaceChildChild : IExampleInterfaceChild { abstract int IExampleInterface.GetValue; // Re-abstracts (i.e. removes the default implementation) for GetValue}
  • "带 DIM 的子接口" 也可以将成员标记为已密封。
  子接口不能为密封成员提供新的实现。如果进行尝试,编译器会发出错误。
  实现类也不能为密封成员提供新的实现,但是编译器允许声明具有相同名称的成员并且不会警告接口方法被隐藏:interface IExampleInterface { sealed int GetValue => 123;}
  class ExampleClass : IExampleInterface { public int GetValue => 456; // No warning}
  class Program { static void Main { var e = new ExampleClass;
  Console.WriteLine(e.GetValue); // Prints 456 Console.WriteLine(((IExampleInterface) e).GetValue); // Prints 123 }}
  •"密封接口成员"
  高级模式匹配
  此版本的 C# 添加了更多模式匹配功能。
  切换表达式允许"切换"一个变量以产生一个新值:// Assuming "user" is a variable of type "User":var salary = user switch { Manager m when m.ManagerialLevel is ManagerialLevel.CLevel => 100_000, // C-Level managers get 100,000 Manager m when m.ManagerialLevel is ManagerialLevel.UpperManagement => 70_000, // Upper managers get 70,000 Manager _ => 50_000, // All other managers get 50,000 _ => 30_000 // Everyone else gets 30,000};
  • "切换表达式" 新的属性模式允许更简洁的方法来匹配对象的属性:var salary = user switch { Manager { ManagerialLevel: ManagerialLevel.CLevel } => 100_000, // C-Level managers get 100,000 Manager { ManagerialLevel: ManagerialLevel.UpperManagement } => 70_000, // Upper managers get 70,000 Manager _ => 50_000, // All other managers get 50,000 { LastAppraisal: { Rating: 10 } } => 40_000, // Users whose last appraisal gave them a 10/10 rating get 40,000 _ => 30_000 // Everyone else gets 30,000};
  • "属性模式" 当类型提供Deconstruct方法(包括元组)时,我们可以使用位置模式来代替:// For sake of example, imagine User has a method declared with the signature:// "public void Deconstruct(out int lastAppraisalRating, out bool isOverEighteen)"
  var salary = user switch { Manager m when m.ManagerialLevel is ManagerialLevel.CLevel => 100_000, Manager m when m.ManagerialLevel is ManagerialLevel.UpperManagement => 70_000, Manager => 50_000, User (10, true) => 45_000, // Users with a 10/10 rating who are also over 18 get 45,000  User (9, true) => 40_000, // Users with a 9/10 rating who are also over 18 get 40,000  User (8, true) => 35_000, // Users with an 8/10 rating who are also over 18 get 35,000 _ => 30_000};
  • "使用解构切换表达式" 如果您只想以位置模式解构对象,则类型说明符是可选的:var salary = user switch { (10, true) => 45_000, (9, true) => 40_000, (8, true) => 35_000, _ => 30,000};
  • "使用元组切换表达式" 上面描述的所有模式也可以在"传统的"switch 语句中使用。
  IAsyncEnumerable
  简而言之,此功能允许遍历等待项的枚举(即Task
  假设DelayedSequence是一个实现IAsyncEnumerablevar delayedSequence = new DelayedSequence(5, TimeSpan.FromSeconds(1d)); // Sequence of 1 to 5 with one second delay between each iteration
  await foreach (var i in delayedSequence) { Console.WriteLine(i);}
  • "简单的异步可枚举示例" await foreach告诉编译器我们要在执行循环体之前等待延迟序列 的每次迭代。IAsyncEnumerable < T> 每次迭代都会返回一个ValueTaskawait foreach (var i in someAsyncEnumerable.WithCancellation(someToken)) { /* ... */}
  • "将 CancellationToken 传递给异步迭代" 此功能的最大优势可能是编写异步生成器的能力,这是创建IAsyncEnumerablepublic async IAsyncEnumerable GetDataPaginated([EnumeratorCancellation] CancellationToken cancellationToken = default) { var paginationToken = new PaginationToken;
  try {  var results = await _database.GetNextResultBatch(paginationToken, cancellationToken); if (!results.ContainsValues) yield break; else yield return results.Batch; } catch (TaskCancelledException) { yield break; }}
  "异步生成器" 在这个实现中,我们通过await从数据库中读取一批新项目来 构造一个异步生成器;然后要么完成迭代(yield break),要么传递下一个要迭代的DataBatch 。 编译器将自动为我们将其转换为IAsyncEnumerable
  需要参数来告诉编译器该参数是我们在通过WithCancellation方法迭代返回的IAsyncEnumerable
  索引和范围
  此功能将两种新类型添加到框架中,它们可以协同工作,即Index和Range,以及两种新的相应语法。
  指数
  索引表示集合或某种可枚举的元素索引 。它没有任何链接或对任何特定可枚举/集合的引用;相反,它只是一个独立的值。
  索引es 可以指定为从集合开头的偏移量(如传统)或从结尾:var characterArray = new { "A", "B", "C", "D", "E", "F", "G" };
  Index indexA = 0; // characterArray[indexA] is "A"Index indexB = 3; // characterArray[indexB] is "D"Index indexC = ^0; // characterArray[indexC] throws an IndexOutOfRangeExceptionIndex indexD = ^3; // characterArray[indexD] is "E"
  Index indexA2 = Index.Start; // characterArray[indexA2] is "A"Index indexB2 = Index.FromStart(3); // characterArray[indexB2] is "D"Index indexC2 = Index.End; // characterArray[indexC2] throws an IndexOutOfRangeExceptionIndex indexD2 = Index.FromEnd(3); // characterArray[indexD2] is "E"
  "索引创建示例"
  指定索引的最简单方法是通过整数的隐式转换;它创建一个索引,指定从可枚举/集合开始的偏移量。indexA和indexB都证明了这一点。Index.Start等价于(Index) 0或Index.FromStart(0)。
  但是,Index es 也可以指定从可枚举/集合末尾的偏移量。indexC和indexD都证明了这一点。^N语法表示我们正在创建一个从末尾倒数的索引。^0指向任何给定可枚举/集合的"结束"的一个元素;因此为什么characterArray[^0]会引发异常。^1将始终为您提供最后一个元素。Index.End等价于^0或Index.FromEnd(0)。在上面的示例中,indexA2与 indexA 相同, indexB2与 indexB相同,等等。
  一些人(包括我)最初对^0索引集合末尾的一个元素感到惊讶。但是在处理范围时它很有意义。实际上,我在 2018 年写了一些关于此的文章:C# 8 Concerns - A Followup[10]。 Index类型 的一些其他成员:// The Value and IsFromEnd properties can be used to deconstruct the index:
  Console.WriteLine($"Index A: {indexA.Value}{(indexA.IsFromEnd ? " (from end)" : "")}"); // Index A: 0Console.WriteLine($"Index B: {indexB.Value}{(indexB.IsFromEnd ? " (from end)" : "")}"); // Index B: 3Console.WriteLine($"Index C: {indexC.Value}{(indexC.IsFromEnd ? " (from end)" : "")}"); // Index C: 0 (from end)Console.WriteLine($"Index D: {indexD.Value}{(indexD.IsFromEnd ? " (from end)" : "")}"); // Index D: 3 (from end)
  // GetOffset will tell you what value the Index translates to for a collection of a given length:
  Console.WriteLine($"Index A in characterArray: {indexA.GetOffset(characterArray.Length)}"); // Index A in characterArray: 0Console.WriteLine($"Index B in characterArray: {indexB.GetOffset(characterArray.Length)}"); // Index B in characterArray: 3Console.WriteLine($"Index C in characterArray: {indexC.GetOffset(characterArray.Length)}"); // Index C in characterArray: 7Console.WriteLine($"Index D in characterArray: {indexD.GetOffset(characterArray.Length)}"); // Index D in characterArray: 4
  全屏查看代码[11]• "索引其他成员"
  因为"从头开始"的索引在内部表示为 Index 结构中的负整数,所以Index的值永远不会是负数。
  Range
  一个Range实例包含两个Index;一个开始和一个结束。
  请注意,此处讨论的Range结构位于System命名空间中。System.Data中还有另一个不相关的Range类型。var characterArray = new { "A", "B", "C", "D", "E", "F", "G" };
  var rangeA = 0..3; // characterArray[rangeA] is ["A", "B", "C"]var rangeB = 3..^0; // characterArray[rangeB] is ["D", "E", "F"]var rangeC = 0..^0; // characterArray[rangeC] is ["A", "B", "C", "D", "E", "F", "G"]var rangeD = 4..^4; // characterArray[rangeD] throws ArgumentOutOfRangeException
  var rangeA2 = Range.EndAt(3);var rangeB2 = Range.StartAt(3);var rangeC2 = Range.All;var rangeD2 = new Range(4, ^4);
  var rangeA3 = ..3;var rangeB3 = 3..;var rangeC3 = ..;
  "范围创建示例"
  创建Range的最简单方法是使用..语法(称为Range 运算符);每边都有一个索引。运算符左侧的索引是包含开始索引,而运算符右侧的索引是排他结束索引。
  如果要创建Start为0的范围,可以省略第一个参数(请参阅rangeA3)。
  如果要创建End为^0的范围,可以省略第二个参数(请参阅rangeB3)。
  这两个快捷方式可以组合起来创建一个代表所有元素的范围(请参阅rangeC3)。
  像以前一样,rangeA与rangeA2相同(与rangeA3 一样),等等。
  请记住,^N是一种创建索引的语法,该索引表示从给定可枚举/集合末尾开始的N个值。因此,范围0..^0表示任何可枚举/集合中的每个项目。这与Range.All和..相同。 Range类型 的一些其他成员:var characterArray = new { "A", "B", "C", "D", "E", "F", "G" };
  var range = ^5..7;
  Console.WriteLine(range.Start); // ^5Console.WriteLine(range.End); // 7Console.WriteLine(range.GetOffsetAndLength(characterArray.Length).Offset); // 2Console.WriteLine(range.GetOffsetAndLength(characterArray.Length).Length); // 5
  "范围其他成员"
  请注意,如果给定的集合长度太小而无法容纳目标范围,则GetOffsetAndLength将引发ArgumentOutOfRangeException 。
  支持索引和范围的类型
  数组具有对Range的 内置支持,如上所示。使用范围创建子数组会返回一个新数组,其值是从原始数组中复制的:var characterArray = new { "A", "B", "C", "D", "E", "F", "G" };
  // This line is translated by the compiler to: var subArray = RuntimeHelpers.GetSubArray(characterArray, 1..^1);var subArray = characterArray[1..^1];
  characterArray[3] = "X"; // Altering values in the original array does not affect the subArray
  Console.WriteLine(subArray.Length); // 5Console.WriteLine(String.Join(", ", subArray.Select(c => """ + c.ToString + """))); // "B", "C", "D", "E", "F"
  •"数组范围支持" 此外,任何具有公共Count或Length的类型都可以自动支持Index es,如果它们提供索引运算符和Range es,如果它们提供带有签名Slice(int, int)的方法:// This class has everything required for automatic Index and Range supportclass NumberLine { public int StartValue { get; } public int Length { get; }
  public NumberLine(int startValue, int length) { StartValue = startValue; Length = length; }
  public int this[int index] { get { if (index >= Length) throw new ArgumentOutOfRangeException(nameof(index)); return StartValue + index; } }
  public IEnumerable Slice(int offset, int length) { if (offset + length > Length) throw new ArgumentOutOfRangeException(nameof(length)); for (var i = 0; i < length; ++i) yield return StartValue + offset + i; }}
  // Here we demonstrate the automatic supportvar numberLine = new NumberLine(0, 10);
  Console.WriteLine(numberLine[Index.Start]); // 0Console.WriteLine(numberLine[Index.FromEnd(1)]); // 9
  Console.WriteLine(String.Join(", ", numberLine[..])); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9Console.WriteLine(String.Join(", ", numberLine[3..7])); // 3, 4, 5, 6Console.WriteLine(String.Join(", ", numberLine[^7..^3])); // 3, 4, 5, 6
  • "自动索引和范围支持" 框架中的一些其他类型也提供自动索引和/或范围支持,包括Span
  RAII 风格的 using 语句
  此功能允许声明应在封闭范围结束时自动释放的变量:void Test { using var fileStream = File.OpenRead("somefile.txt");
  // ...
  // fileStream.Dispose automatically invoked here at the end of this method}
  • "RAII 使用语句"
  静态局部函数
  这种面向性能的特性允许您确保本地函数不捕获[12]任何变量。
  以非静态局部函数(CreateUserDetailsS tring()中的CombineData() )为例:class User { public string Name { get; }
  public string PermanentData { get; }
  public string CreateUserDetailsString(string additionalData) { string CombineData { return PermanentData + additionalData; }
  return $"{Name} ({CombineData})"; }}
  • "非静态局部函数示例" CombineData从其本地范围之外捕获两个变量,additionalData和this(这使它可以访问this.PermanentData)。在性能敏感的场景中,变量捕获会增加垃圾收集器的压力,这是有害的。
  将局部函数声明为静态将导致编译器不允许捕获任何变量。反过来,这将导致编译器错误,直到程序员手动将这些变量传递给本地函数。以当前形式将CombineData
  标记为静态将产生两个编译器错误,告诉我们不能引用它并且我们不能引用additionalData。为了解决这个问题,我们必须像标准方法调用一样传入我们想要使用的参数:public string CreateUserDetailsString(string additionalData) { static string CombineData(string permanentData, string additionalData) { return permanentData + additionalData; }
  return $"{Name} ({CombineData(PermanentData, additionalData)})";}
  •"静态局部函数示例"
  只读结构成员
  这种面向性能的添加允许将结构的特定成员标记为不可修改/不可修改。
  正如前面[13]在参数中讨论的那样,只读结构对于允许编译器灵活地不创建参数的防御性副本很重要。但是,有时结构必须是可变的并且不能标记为readonly。此功能允许将结构的某些部分设为只读,因此允许编译器在某些情况下仍然避免防御性副本。
  在这种情况下,只读成员在某种程度上可以比作C++ 中的const成员。struct MyStruct { public int Alpha { get; set; }
  // Readonly property public readonly int Bravo { get; }
  public void IncrementAlpha { Alpha += 1; }
  // Readonly method: Does not alter any state in this struct public readonly void PrintBravo { Console.WriteLine(Bravo); }}
  • "只读结构成员示例" 尝试将IncrementAlpha标记为只读将导致引发编译器错误,因为操作Alpha += 1修改了Alpha。
  空合并赋值
  这个小功能允许您仅在变量为空时使用简洁的语法为变量分配值。以下示例中的两行具有相同的含义:// Classic exampleif (myStr == ) myStr = "Hello";
  // New way with -coalescing assignmentmyStr ??= "Hello";
  •"空合并分配"
  References
  [1]全屏查看代码:https://benbowen.blog/post/two_decades_of_csharp_iv/using_"struct"_constraint_with_able_references-.html
  [2]全屏查看代码:https://benbowen.blog/post/two_decades_of_csharp_iv/using_"class"_constraint_with_able_references-.html
  [3]全屏查看代码:https://benbowen.blog/post/two_decades_of_csharp_iv/using_able_interface_constraint.html
  [4]全屏查看代码:https://benbowen.blog/post/two_decades_of_csharp_iv/using_not_constraint.html
  [5]保留属性有助于编译器的空状态静态分析:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/able-analysis
  [6]
  img
  : https://img2022.cnblogs.com/blog/191302/202209/191302-20220904224809805-1987561416.png
  [7]语言设计会议的笔记中:https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-12-18.md#var
  [8]显式实现:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/explicit-interface-implementation
  [9]最终在发布前放弃了:https://github.com/dotnet/csharplang/blob/6b5a397cc4faef6d481d9bace13d7e3c19de63bb/meetings/2019/LDM-2019-04-29.md#conclusion
  [10]C# 8 Concerns - A Followup:https://benbowen.blog/post/csharp_8_concerns_followup/#range_operator_and_"hat"_operator
  [11]全屏查看代码:https://benbowen.blog/post/two_decades_of_csharp_iv/index_additional_members.html
  [12]捕获:https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions#capture-of-outer-variables-and-variable-scope-in-lambda-expressions
  [13]正如前面:https://benbowen.blog/post/two_decades_of_csharp_iii/#in_parameters,_readonly_structs,_readonly_ref_returns

意大利杯比分排名佛罗伦萨VS都灵!意大利杯集锦回放LDSPORTS佛罗伦萨是意甲的球队,20轮联赛积24分排名在12。佛罗伦萨最近2次对阵都灵全部未能取得佳绩。意甲球队都灵在20轮比赛中积27分排名在8。都灵在本赛季的意大利杯中整首次留洋登陆欧洲顶级联赛,小太阳张景胤已做好迎接挑战的准备钱江晚报小时新闻记者杨渐通讯员余敏刚2月2日,中国男排在福建漳州体育训练基地开启了2023年首次集训,入选国家队的6名浙江体彩队员中却唯独少了主攻张景胤的身影。而暂时不报到的张景胤我国北方的两个海岛县,都有长山岛,被誉为海上仙境来过吗?在我们伟大祖国辽阔的海域分布着八大群岛,有厦门市舟山市三沙市等三个著名的地级以上海岛市,还分布着13个海岛县。你知道吗?在我国北方就有两个海岛县,而且也有着两座长山岛,被誉为海上花意媒罗马仍未与扎尼奥洛和解,但准备欢迎卡尔斯多普回归直播吧2月3日讯据IlTempo报道称,罗马仍然没有与扎尼奥洛和解,但卡尔斯多普即将回归。扎尼奥洛和卡尔斯多普都在寻求于冬窗离开罗马,但是都没有成功。据IlTempo报道称,与扎尼欧洲央行继续大幅加息短期或加大经济下行压力新华社法兰克福2月3日电(记者单玮怡)欧洲央行2日宣布将欧元区三大关键利率均上调50个基点,并重申将维持资产负债表缩减规模,继续保持货币紧缩步伐。分析人士认为,欧洲央行持续大幅加息佩杜拉加拉塔萨雷有意租借扎尼奥洛,罗马要求加入强制买断条款直播吧2月3日讯据意大利记者佩杜拉透露,土耳其俱乐部加拉塔萨雷有意租借扎尼奥洛,但罗马要求加入强制买断条款。扎尼奥洛冬季离队不成,但他和罗马的关系已经破裂,继续留在罗马可能被封杀。发现孩子抽烟如何跟他沟通?看看过来人怎么做的!发现孩子抽烟如何跟他沟通?大部分孩子抽烟是因为青春期对很多事物的好奇心,其实孩子本质并没有学会,所以父母千万不要动不动就打骂孩子。作为家长,要正确的引导孩子,吸烟有害健康,尤其对青了解孩子不同时期的阅读爱好,让孩子爱上绘本阅读什么是早期阅读孩子的阅读开始越早,阅读时思维过程越复杂,阅读时对智力发展就越有益。苏霍姆林斯基幼儿阅读以画面为主,阅读的目的是学习如何阅读,培养兴趣,养成习惯,获得策略。早期阅读对第三代试管婴儿技术是什么??欢迎关注碱基序列,任何疑问请私信!胚胎植入前遗传学检测(Preimplantationgenetictesting,PGT),该检测运用于试管婴儿治疗中,通过在胚胎植入前对胚胎进行胡鑫宇被认定自杀,为什么现在自杀的孩子越来越多?作者赵小明编辑排版杨丽雲小明语录只有知道孩子的语言对上话,他们的世界才会向你开放,否则就只是假装被你咨询。01学习过度的恶果过度学习有两个恶果一是孩子失去了童年时间没有同伴玩耍的时带孩子移居厄瓜多尔必读公立私立国际学校的优缺点分析最近不少家长带孩子移民厄瓜多尔,或是以厄瓜多尔为跳板实现更大的移民目标,所以对厄瓜多尔的教育非常关注。之前南瓜君介绍过基多的几所国际学校,但是对于厄瓜多尔总体的教育情况并没有进行总
戴尔OptiPlex7000Tower商务台式机多媒体创作全能手日常办公还有音视频创作领域中,我们非常重视电脑的性能是否能够满足我们的生产力需求,以及它的后期维护是否足够简单便捷,售后是否专业无忧。这个时候,选择大品牌的专业办公主机就成为不少用睡醒不想上班了?西蒙斯确认无缘第四场,缺席理由遭球迷各种调侃2022年4月26日,布鲁克林篮网队将会在主场迎战波士顿凯尔特人,这是两队系列赛的第四场比赛。此前篮网队以0比3落后,所以这一场比赛也是篮网队的生死之战。此前多家媒体报道称西蒙斯会知名媒体人曝国足离谱做法国足陪陈戌源踢球,足协一把手狂进球在最新一轮亚冠小组赛中,泰山队年轻球员的精彩表现犹如一股春风,吹进了国内球迷的心里,瞬间让人有了一种看到希望的感觉。虽然与狮城水手的第二次交锋以输球结束,但是泰山队卢永涛的点球和刘46岁余秀华凌晨发文爆粗口,刘信达喊话残联有辱斯文余秀华和90后男友杨槠策恋爱后,特别喜欢在网上秀恩爱,仿佛从一个中年女性,变成了有人疼爱的小公主。被爱是一件很浪漫的事,她想把幸福传递给网友,本身也没什么错。不过,由于余秀华和男友6位放弃央视ampampquot铁饭碗ampampquot当演员的明星,有的成影后,有的被骗光财产央视主持人和演员两个职业摆在你面前,你会选择哪个?在娱乐圈里有这样6位演员,他们曾经顶着央视主持人的光环,在荧幕前大显身手。用那一口流利的普通话一副端庄又大气的主持风格,征服了无数湖人票选主帅出炉!纳斯榜首,库里恩师紧随其后,奥尼尔毛遂自荐湖人无缘附件赛,詹姆斯看到其他球队进入季后赛,非常沮丧,旅游之时也不忘关注NBA季后赛,而对于湖人而言,他们解雇沃格尔之后,球队面临选帅重任,谁能成为湖人下一任主教练,一直成为球迷一腔热爱四十载,六十岁初心如少年记乒乓球国际级裁判员郎敬初见郎敬,他给人的印象是身姿矫健挺拔帅气,精神气质完全不像是60多岁的人。尤其是每当聊起乒乓球,他时而神采飞扬,时而严肃真诚,热爱之情溢于言表。历尽千帆,归来仍是少年,这句话是对他迷茫,幼小衔接到底该怎么做?今天看到教育部发了个文件,要求要合理设计小学一二年级课程,注重幼小衔接。我觉得非常有必要,幼儿园阶段素质教育,不学学科知识,按现在小学一年级的学习计划孩子能跟上吗?我家也有个上大班渐行渐远,曾经的死神杜兰特为何在季后赛迷茫了1哈登远走费城在杜兰特接连受伤欧文不肯接种疫苗的情况下,篮网的从东部第一一下子掉到东部第十。为了追求总冠军,哈登远走费城与恩比德组成二人组。上个赛季,篮网40横扫绿军,如今哈登出走建议大家逛超市看到这2种洗发水,别错过!虽然丑但好用国货牛有些洗发水,是真的别再用了,掉发出油都来了之前一直用的洗发水又贵又掉发,还都是大牌别再只知道那些又贵又不好用的洗发水了,国货越来越好,一直用的国货洗发水,洗完头皮干净用着头发三四天今天大幅放量下跌,五一前振荡筑底,被错杀的股票会有表现质优股202204251636今天行情可以说是2019年4月以来,三年来最恐怖的一天了,开盘大幅低开(受美国股市暴跌影响,加上惯性下跌影响)全天单边暴跌,盘中几次出现钓鱼线的走势(