开心码农 - .NET
type: channel
link: https://sharkyzh.cn/index.php/category/NET/
description: .NET
items: C# 语言中的 Lambda 表达式和匿名函数
items: .NET中的定时器
items: C# 中 object 与 dynam
items: C#中的Marshal类
items: C#中的垃圾回收机制
items: C#中值类型与引用类型在GC中的区别
items: C#中的struct
items: Reactive UI反应式编程UI框架入门学习
items: C# 发展历史（各版本特性）
items: C# 9.0新特性：记录(record)和with表达式

C# 语言中的 Lambda 表达式和匿名函数
type: item
date: 2024-08-03T19:40:30+08:00
description: Lambda 表达式和匿名函数是 C# 开发中的基本功能，为我们提供了简洁灵活的代码构造。在这篇文章中，我将讨论 lambda 表达式的基础知识，包括它们的语法、使用场景、最新的 C# 12 功能和其他一些高级功能。Lambda 表达式基础知识表达式 lambda表达式 lambda 遵循一个简单的模式：（input-parameters） => expression。例如，（x） => x * x 将输入值 x 平方。这些 lambda 通常用于各种方案，例如 LINQ 查询、使用 Task.Run（） 进行任务计划以及异步编程。LINQ 查询// List of integers List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; // Using a lambda expression in a LINQ query to filter even numbers var evenNumbers = numbers.Where(x => x % 2 == 0); // Output the result foreach (var number in evenNumbers) { Console.WriteLine(number); }Lambda 表达式用于从列表中筛选偶数。(x => x % 2 == 0)任务调度Task.Run()// Using Task.Run with an expression lambda to execute a task asynchronously Task.Run(() => { // Perform some time-consuming operation for (int i = 0; i < 5; i++) { Console.WriteLine($"Task is running: {i}"); Thread.Sleep(1000); // Simulate some work } });在 的任务调度中，表达式 lambda 用于定义异步运行的代码块。Task.Run()语句 lambdas相比之下，语句 lambda 将它们的身体包裹在大括号内：(input-parameters) => { <sequence-of-statements> }它们为更复杂的逻辑和执行多个语句提供了一个平台。与表达式 lambda 不同，它们在处理控制流和变量范围方面提供了更大的灵活性。例如，请考虑以下事项：Action<string> greet = name => { string greeting = $"Hello {name}!"; Console.WriteLine(greeting); }; greet("Niraj");在这里，lambda greet 接受一个字符串参数，使用该参数构造一个问候消息，然后将其打印到控制台。Lambda 表达式的输入参数单一输入参数Lambda 表达式可以具有单个或多个输入参数。在处理单个输入参数时，语法简洁明了，并为声明提供了可选的括号，从而实现了灵活性和可读性。可以声明单个参数，也可以不带括号：或 。虽然括号是可选的，但它们有助于提高代码的清晰度和组织性。x => x x(x) => x x多个输入参数多个参数之间用逗号分隔：。- 显式和隐式类型推断提高了代码的清晰度。(x, y) => x + yFunc<int, int, bool> testForEquality = (x, y) => x == y;这行简单的代码声明了一个名为 的委托，该委托表示一个函数，该函数采用两个整数参数并返回一个布尔值。该表达式定义了一个匿名函数，如果第一个参数等于第二个参数，则返回该函数，否则返回。testForEquality(x, y) => x == ytruexyfalse委托类型和转换Lambda 表达式可以转换为 Action 和 Func 等委托类型，从而有助于创建动态和简洁的代码结构。// Lambda expression converted to Action delegate Action<int> printNumber = x => Console.WriteLine(x); printNumber(10); // Output: 10 // Lambda expression converted to Func delegate Func<int, int, int> addNumbers = (x, y) => x + y; int result = addNumbers(5, 3); // result = 8Action 委托用于具有 void 返回类型的方法，而…

.NET中的定时器
type: item
date: 2024-08-03T08:33:00+08:00
description: .NET中至少有6种定时器，每一种定时器都有它的用途和特点。根据定时器的应用场景，可以分为UI相关的定时器和UI无关的定时器。本文将简单介绍这6种定时器的基本用法和特点。UI定时器.NET中的UI定时器主要是WinForm、WPF以及WebForm中的定时器。分别为：System.Windows.Forms.TimerSystem.Windows.Threading.DispatcherTimerSystem.Web.UI.Timer通常情况下，WinForm、WPF中的定时器是在UI线程上执行回调函数，因此可以直接访问UI元素。由于WinForm、WPF支持单线程单元模型（Single-Thread Apartment，STA），定时器间隔事件是在UI线程上触发，因此，不用担心线程安全问题。System.Web.UI.Timer是通过Javascript定时器和服务端异步回调实现，也是单线程的。请注意，这里说的是通常情况，后边介绍System.Windows.Threading.DispatcherTimer时会提到在非UI线程创建DispatcherTimer时也无法直接访问UI元素。System.Windows.Forms.TimerSystem.Windows.Forms.Timer针对WinForm应用进行了优化，是只能在WinForm上使用的定时器。这个定时器是针对单线程环境设计的，是在UI线程上处理定时任务。它要求用户代码有可用的UI消息泵，定时任务须在UI线程上运行，或者跨线程通过Invoke或者BeginInvoke封送(marshal)到UI线程上运行。其优点是使用简单，只需通过给Interval属性赋值来设置时间间隔，并注册Tick事件处理定时任务。其缺点是精度不高，精度为55毫秒，也就是Interval赋值小于55时，也是55毫秒触发一次定时任务。public partial class TimerFrom : Form { private System.Windows.Forms.Timer digitalClock; private void TimerFrom_Load(object sender, EventArgs e) { digitalClock = new System.Windows.Forms.Timer();//创建定时器 digitalClock.Tick += new EventHandler(HandleTime);//注册定时任务事件 digitalClock.Interval = 1000;//设置时间间隔 digitalClock.Enabled = true; digitalClock.Start(); //开启定时器 } public void HandleTime(Object myObject, EventArgs myEventArgs) { labelClock.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } private void frmTimerDemo_FormClosed(object sender, FormClosedEventArgs e) { digitalClock.Stop();//停止定时器 digitalClock.Dispose(); } }System.Windows.Threading.DispatcherTimerSystem.Windows.Threading.DispatcherTimer是WPF中的定时器，它是基于Dispatcher对象的(并不是基于UI线程的)。DispatcherTimer的定时任务是像其他操作一样放在Dispatcher队列上，其执行操作时间依赖于队列中其他任务及其优先级，因此，DispatcherTimer不保证在时间间隔发生时准确执行，只保证不会在时间间隔发生前执行。Dispatcher为特定线程维护工作项（操作）的优先级队列，在线程上创建Dispatcher对象时，它成为唯一可以关联该线程的Dispatcher对象，WPF中，DispatcherObject只能被与之关联的Dispatcher对象访问，也就是非UI线程中无法直接访问UI元素（WPF中的UI元素都是派生自DispatcherObject）此外，DispatcherTimer不像System.Windows.Forms.Timer那样只在UI线程上创建才能触发Tick事件，它在非UI线程下创建也可以触发Tick事件，此时访问UI元素也需要通过Invoke或者BeginInvoke封送(marshal)到UI线程上运行。其优点也是简单易用，适合在UI线程上执行任务或触…

C# 中 object 与 dynam
type: item
date: 2024-06-29T12:37:38+08:00
description: 在C#中，object和dynamic类型都提供了一种处理数据的灵活方式，但它们的用途和行为在某些关键方面存在显著差异。了解这些差异有助于更合理地使用这两种类型，并优化代码的性能和可维护性。object 类型定义：object是.NET中所有类型的基类。任何类型的数据都可以赋值给object类型的变量。类型安全：使用object类型时，编译时类型检查是类型安全的。但在运行时，如果需要将object类型转换回原始类型，就需要显式的类型转换或使用as、is操作符，这可能引发运行时错误。性能：频繁地将值类型装箱（赋值给object类型）和拆箱（转换回原始值类型）会影响性能，因为这些操作需要在托管堆上分配和回收内存。dynamic 类型定义：dynamic类型在编译时跳过类型检查，在运行时解析类型信息。这使得编写与COM对象、动态语言（如Python）互操作的代码或使用反射等场景下的代码更为简便。类型安全：dynamic类型不是类型安全的，因为所有关于类型的错误只能在运行时被捕获。这增加了运行时错误的风险，但提供了更大的灵活性。性能：使用dynamic类型可能会导致比object类型更大的性能开销，因为它需要在运行时解析类型信息。这个开销在每次动态操作时都会发生，尤其是在循环或高频调用的场景中更为明显。关键区别编译时类型检查object: 使用object类型时，编译器会进行类型检查。任何类型的数据都可以赋值给object类型的变量，这是通过装箱操作实现的（对于值类型）。从object类型转换回原始类型需要显式的拆箱或类型转换，这在编译时就确定了。如果类型转换不正确，会在编译时捕获到错误。dynamic: 使用dynamic类型时，编译器不会进行类型检查。相反，所有关于该变量的操作都会推迟到运行时解析。这意味着，如果对dynamic类型变量的操作无效，错误只会在运行时被发现。性能object: object类型的性能开销主要源于需要装箱和拆箱操作，尤其是对于值类型。这些操作需要在运行时进行，可能会影响性能，但不涉及运行时类型解析。dynamic: dynamic类型的性能开销来自于运行时的类型解析。每次对dynamic类型的变量进行操作时，都需要动态地检查对象可以执行哪些操作，这比静态类型检查更耗时。使用场景object: 由于其类型安全的特性，object类型适用于需要处理不同数据类型但又希望保持类型安全的场景。例如，作为泛型集合中的元素类型，或在需要在不同类型之间进行显式转换的场景中使用。dynamic: dynamic类型适用于需要高度灵活性的场景，如与动态语言的互操作（例如，Python或JavaScript），或者当访问COM对象时。dynamic类型减少了需要编写的样板代码量，并允许更灵活地处理没有静态类型信息的数据。错误处理object: 类型转换错误可以在编译时被发现，因此使用object类型时，代码通常更安全，更易于调试。dynamic: 由于类型检查被推迟到运行时，因此使用dynamic类型可能会导致运行时错误，这些错误在编译时是不可见的。这使得dynamic类型的代码可能更难调试。使用与优化建议优先考虑类型安全：除非确实需要dynamic类型提供的灵活性，否则应优先使用静态类型或object类型。静态类型在编译时提供错误检查，有助于提高代码的稳定性和可维护性。避免不必要的装箱拆箱：如果频繁处理值类型数据，考虑使用泛型或值类型集合来避免装箱拆箱带来的性能损失。谨慎使用dynamic：在需要处理动态类型数据或与动态语言互操作时，dynamic类型非常有用。但在其他情况下，使用dynamic可能会使代码难以理解和维护，并且增加运行时错误的风险。仅在确实需要动态特性时使用dynamic。性能关键路径避免使用dynamic：在性能敏感的代码路径中，避免使用dynamic类型。考虑使用其他方式，如反射加缓存、委托、表达式树等，来实现所需的动态行为，同时优化性能。

C#中的Marshal类
type: item
date: 2024-06-19T09:23:00+08:00
description: 在C#中，System.Runtime.InteropServices.Marshal类是一个非常重要的类，它提供了一组方法和属性，用于在托管代码与非托管代码之间进行数据的互操作（Interop）。这包括分配和释放非托管内存、复制数据、以及处理非托管函数指针等功能。简而言之，Marshal类是托管代码（.NET 应用程序）和非托管代码（如C++编写的动态链接库DLL）之间的桥梁。Marshal类的一些主要用途包括：内存分配和释放：AllocHGlobal和AllocCoTaskMem方法用于在非托管内存中分配内存。FreeHGlobal和FreeCoTaskMem方法用于释放那些通过AllocHGlobal或AllocCoTaskMem分配的内存块。数据复制：Copy系列方法允许将数据从托管数组复制到非托管内存，或者从非托管内存复制到托管数组中。PtrToStructure和StructureToPtr方法用于将数据从非托管内存块复制到托管结构体，或将托管结构体的数据复制到非托管内存。处理非托管函数指针：GetDelegateForFunctionPointer方法可以将非托管函数指针转换为相应的托管委托。GetFunctionPointerForDelegate方法可以获得与托管委托相对应的非托管函数指针。字符串处理：StringToHGlobalAnsi、StringToHGlobalUni等方法用于将托管字符串转换为非托管内存中的字符串。PtrToStringAnsi、PtrToStringUni等方法用于将非托管内存中的字符串转换为托管字符串。获取非托管类型的大小：SizeOf方法用于获取非托管类型或托管结构体的大小，这在分配非托管内存时非常有用。示例：复制数据到非托管内存以下示例演示了如何使用Marshal类将一个整数数组复制到非托管内存，并最终释放该内存：using System; using System.Runtime.InteropServices; class Program { static void Main() { int[] numbers = { 1, 2, 3, 4, 5 }; int size = Marshal.SizeOf(typeof(int)) * numbers.Length; // 在非托管内存中分配空间 IntPtr ptr = Marshal.AllocHGlobal(size); try { // 将托管数组复制到非托管内存 Marshal.Copy(numbers, 0, ptr, numbers.Length); // 从非托管内存读取数据... } finally { // 释放非托管内存 Marshal.FreeHGlobal(ptr); } } }Marshal类是.NET应用程序中实现托管与非托管代码互操作的关键类，它提供了丰富的API来处理非托管资源，是处理非托管代码的强大工具。

C#中的垃圾回收机制
type: item
date: 2024-06-16T18:05:32+08:00
description: C# 中的垃圾回收（Garbage Collection, GC）机制是自动管理应用程序内存的一个重要组成部分，它自动跟踪和回收不再使用的对象所占用的内存。这一机制使得开发人员不必手动分配和释放内存，降低了内存泄漏的风险，提高了开发效率。以下是C#垃圾回收机制的几个关键方面：堆内存管理C#程序中的对象（即引用类型实例）存储在托管堆（managed heap）上。当创建一个对象时，.NET运行时会为其在堆上分配内存。而值类型，如果作为局部变量，通常存储在栈上；如果作为类或结构体的字段，则同样存储在堆上作为对象的一部分。代际回收.NET的垃圾回收器采用代际回收策略，将堆划分为三个代（Generation 0、1、2），每个代对应不同的回收频率和成本效益比：Generation 0 (Gen 0): 新创建的对象首先被分配在这里。Gen 0的回收最频繁，因为它包含了短生命周期的对象。每次Gen 0回收都会检查并清理不再引用的对象。Generation 1 (Gen 1): 经过一次Gen 0回收仍然存活的对象会被提升到Gen 1。Gen 1的回收频率低于Gen 0。Generation 2 (Gen 2): 经过Gen 1回收仍存活的对象最终进入Gen 2。Gen 2的对象被认为是长寿对象，回收频率最低。垃圾回收触发时机垃圾回收的触发条件包括但不限于：当托管堆空间不足时。当系统资源紧张时。显式调用 GC.Collect() 方法（虽然不推荐，因为GC会根据需要自动执行）。回收过程标记阶段：GC首先执行标记操作，识别出所有可达的对象，即从根对象（如静态变量、线程栈上的局部变量）开始，遍历引用链，标记所有可达的对象。压缩阶段（在某些回收周期中）：回收结束后，为了减少堆碎片，GC可能会执行压缩操作，移动存活的对象，整理堆空间，使其连续。清理阶段：未被标记的对象所占用的内存被回收，释放给堆使用。非确定性延迟垃圾回收的过程是不可预测的，这意味着它可能在任何时刻发生，导致应用程序暂停执行（停顿），这对于实时或高性能应用可能是个问题。.NET提供了低延迟的GC模式（如服务器GC模式）来减轻这个问题。

C#中值类型与引用类型在GC中的区别
type: item
date: 2024-06-16T12:18:09+08:00
description: 在C#中，值类型和引用类型在内存管理，特别是垃圾回收（Garbage Collection, GC）方面表现出不同的行为。值类型值类型（如int、float、struct等）存储在栈（stack）上，或者如果它们是类或结构体的成员，则存储在该对象的堆（heap）内存中。值类型的本质特点是它们直接包含数据，而非指向数据的引用。因此，值类型的生命周期通常与其声明的作用域紧密相关：栈上分配: 在局部作用域内声明的小型值类型变量通常直接分配在栈上。栈内存由操作系统自动管理，超出作用域后，变量占用的内存会自动释放，无需GC参与。堆上分配: 如果值类型是类的字段或数组的元素，它们会被分配在堆上。尽管如此，因为它们不是通过引用访问，所以仍然是值类型。堆上的值类型数据会随着包含它的对象一起被垃圾回收，但垃圾回收机制并不直接管理这些值类型的生命周期，而是管理包含它们的对象。引用类型引用类型（如class、interface、数组、委托等）存储在堆上，并且变量本身（即引用）存储在栈上或其它堆对象中。引用类型的关键在于变量存储的是数据的地址，而非数据本身：垃圾回收管理: 垃圾回收器负责追踪和回收不再被使用的堆内存。当一个引用类型的对象不再有根可达（即没有任何变量引用它）时，GC会在某个不确定的时刻回收其占用的内存，以减少内存泄露并回收资源。代际回收: .NET的垃圾回收器将堆分为多个代，新创建的对象位于第0代，经历多次未被回收的对象会逐渐晋升到更高代。不同代别的对象有不同的回收策略，这有助于提高垃圾回收的效率。总结值类型的内存管理更加直接，尤其是栈上分配的值类型，其内存自动随作用域结束而释放，不需要垃圾回收机制介入。引用类型的内存管理依赖于垃圾回收器，GC负责监控堆内存，找出不再使用的对象，并回收它们占用的资源，这一过程相对间接且引入了一定的运行时开销。了解这些差异有助于开发者在设计程序时做出更合理的类型选择，以优化性能和内存使用。

C#中的struct
type: item
date: 2024-06-16T12:03:00+08:00
description: 在C#中，struct 是一种值类型，它用于定义轻量级的对象，这些对象通常用于存储少量数据。与引用类型（如类 class）不同，结构体是直接存储在栈上的，或者作为其他类型的一个字段时，存储在包含类型的实例中。这意味着，当你传递或赋值一个结构体时，会复制整个结构体的内容，而不是仅仅复制一个指向对象的引用。这在某些场景下可以提高效率，并避免了引用类型可能导致的副作用。结构体的特点：值类型：结构体是值类型，意味着它们在内存中直接存储其数据，当作为参数传递或赋值时，会创建一份拷贝。默认构造函数：结构体有默认的无参构造函数，用于初始化所有字段为它们的默认值（例如，数字为0，引用类型为null）。可以有构造函数：尽管结构体默认有一个无参构造函数，你也可以定义自己的构造函数，但是需要注意的是，结构体不能有默认的无参数构造函数以外的默认构造函数（即没有参数列表的构造函数），且构造函数必须初始化所有的字段。可以有方法、属性、索引器、事件：与类相似，结构体也可以包含方法、属性、索引器和事件，但通常推荐仅在必要时才这么做，因为结构体主要用于轻量级的数据封装。继承性：结构体不能从另一个结构体或类继承，也不能作为基类被其他类或结构体继承。所有结构体都隐式继承自 System.ValueType，后者又继承自 System.Object。密封性：结构体是密封的，意味着不能被其他类型继承。性能考虑：由于结构体是值类型，所以在栈上分配时速度很快，适合于那些频繁创建和销毁的小型数据结构。但是，如果结构体过大或者包含大型对象（如数组、大字符串等），则可能不如引用类型高效，因为每次复制都会涉及大量数据。装箱和拆箱：当结构体用作对象类型时（例如，存储在Object类型的变量中或作为泛型参数），会发生装箱操作，将结构体转换为对象引用。相反的过程称为拆箱。装箱和拆箱操作会带来性能开销。使用场景：轻量级数据结构：当需要存储一些简单的数据组合，比如坐标、颜色、日期时间等，且不需要复杂的继承或多态行为时，使用结构体更为合适。性能敏感的场景：在对性能要求极高的循环或频繁操作中，使用结构体可以减少垃圾回收的压力。在C#中，struct 和 class 都用于定义对象，但它们之间存在几个关键的区别，主要涉及内存管理、继承、默认值、赋值行为等方面。以下是两者之间的一些核心差异：1. 内存管理struct（结构体）：结构体是值类型，它们通常直接存储在栈上或作为其他类型的一部分存储在堆上。当结构体作为方法的参数传递或被赋值给另一个变量时，会创建结构体内容的一份拷贝。这意味着修改一个结构体变量不会影响到原始数据。class（类）：类是引用类型，它们存储在堆上，而变量通常只包含对对象的引用（一个指针）。当类的对象被传递或赋值时，实际上只是复制了对象的引用，而不是对象本身。因此，多个变量可能指向同一个对象，修改其中一个变量的内容会影响到其他所有指向同一对象的变量。2. 默认值struct：结构体的每个字段都会被初始化为其默认值（如 int 类型默认为0，bool 类型为false，引用类型为null等），即使没有显式初始化。class：类的实例如果没有通过构造函数初始化，其字段将保持未定义状态，且不能直接访问未实例化的类对象。3. 继承struct：结构体不能从另一个结构体或类继承，也不可以被其他结构体或类继承。所有结构体隐式继承自 System.ValueType，而 System.ValueType 继承自 System.Object。class：类可以继承自其他类（除了密封类），并且可以作为基类被其他类继承，支持多层继承和接口的实现。4. 构造函数struct：虽然结构体可以有构造函数，但它们只能有带有参数的构造函数。结构体没有默认的无参构造函数，除非你显式定义一个。此外，调用结构体的构造函数通常是通过初始化语法完成的。class：类既可以有无参数构造函数也可以有带参数的构造函数，并且如果不显式定义任何构造函数，编译器会自动提供一个默认的无参构造函数。5. 性能考量struct：由于结构体是值类型，对于小型数据结构来说，它们的分配和处理通常更快，尤其是在栈上分配时。但在大型结构体或含有大型对象成员的情况下，频繁的

Reactive UI反应式编程UI框架入门学习
type: item
date: 2024-05-28T21:37:08+08:00
description: 数据绑定在强绑定方式中，需要给控件定义他的Name属性。 在界面后台的cs文件中使用强绑定方式。//BtnContent是ViewModel中的属性，btnOpenFile是界面中的控件，并指定控件需要绑定的依赖属性`` ``this``.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content); 在Reactive UI框架中，提供了单向绑定和双向绑定两种绑定类型，上述代码中的OneWayBind是属于ViewModel->View的单向绑定，另外还有一个API Bind则是双向绑定。this.Bind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content);之所以官方推荐这样的绑定方式，是因为框架中提供了一个管理ViewModel生命周期的API WhenActivated，解决了Xaml弱绑定方式带来的内存泄露的可能性。在WhenActivated API的函数回调中进行绑定属性和Command，可以同步跟踪View和对应绑定属性的生命周期，避免发生内存泄露。 this.WhenActivated(dispos => { this.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content).DisposeWith(dispos); });WhenActivated 会在View被激活时同步调用注册的回调函数，注意，在OneWayBind后面新增了一个API调用DisposeWith,他可以确保当界面被销毁时，对应的viewModel及其绑定的属性和命令也会被销毁。类似的，绑定Commond this.WhenActivated(dispos => { this.OneWayBind(ViewModel, vm => vm.BtnContent, vw => vw.btnOpenFile.Content).DisposeWith(dispos); this.BindCommand(ViewModel, viewModel => viewModel.OpenPage, view => view.openButton) .DisposeWith(disposableRegistration); }); 这样的强绑定相比于Xaml中的弱绑定，会有以下的优势：1.提供了ViewModel的生命周期管理，避免内存泄露。2.控件和后台属性的对应关系更为直观，提高代码的可阅读性。当然也有一定的缺陷，会增加代码量，并且增加View和ViewModel的耦合性。定义属性和命令在MVVMLight中定义一个带通知的属性和Commond： private string content ; public string Content { get { return content; } set { content = value; RaisePropertyChanged(() => Content); } } private RelayCommand openFileCommand = null; public RelayCommand OpenFileCommand { get { return openFileCommand = openFileCommand ?? new RelayCommand(OpenFile); } }在ReactiveUI中也通成功了类似RaisePropertyChanged和RelayCommand功能的API，RaiseAndSetIfChanged和ReactiveCommand。 private string content; public string Content { get { return content; } set { this.RaiseAndSetIfChanged(ref content,value); } } private ReactiveCommand<Unit, Unit> openFileCommand; public ReactiveCommand<Unit, Unit> OpenFileCommand { get { return openFileCommand = openFileCommand ?? ReactiveCommand.Create(() => { }); } }其中ReactiveCommand的两个Unit，前一个是传入参数，后一个是返回值。ReactiveCommand的定义与MVVMLight大同小异。但是在R…

C# 发展历史（各版本特性）
type: item
date: 2024-05-20T20:13:00+08:00
description: C# 版本 12发布时间：2023 年 11 月C# 12 中增加了以下功能：主构造函数 - 可以创建任意 class 或 struct 类型的主构造函数。集合表达式 - 一种用于指定集合表达式的新语法，包括 spread 元素 (..e)，可展开任何集合。内联数组 - 使用内联数组，你可以创建固定大小的 struct 类型数组。Lambda 表达式中的可选参数 - 可以为 Lambda 表达式的参数定义默认值。ref readonly 参数 - ref readonly 参数可以让可能使用 ref 参数或 in 参数的 API 更清晰。创建任何类型的别名 - 可以使用 using 别名指令创建任何类型的别名，而不仅仅是命名类型。试验性属性 - 指示试验性功能。拦截器 - 已作为预览功能发布。总体而言，C# 12 提供的新功能可让你更高效地编写 C# 代码。 你已经知道的语法可以在更多地方使用。 其他语法可实现相关概念的一致性。C# 版本 11发布时间：2022 年 11 月C# 11 中增加了以下功能：原始字符串字面量泛型数学支持泛型属性UTF-8 字符串字面量字符串内插表达式中的换行符列表模式文件本地类型必需的成员自动默认结构常量 string 上的模式匹配 Span扩展的 nameof 范围数值 IntPtrref 字段和 scoped ref改进了方法组向委托的转换警告波 7C# 11 引入了泛型数学以及支持该目标的几个功能。 可以为所有数字类型编写一次数值算法。 有更多功能可简化 struct 类型处理，例如所需成员和自动默认结构。 使用原始字符串文本、字符串内插中的换行符和 UTF-8 字符串文本可以更轻松地处理字符串。 文件本地类型等功能使源生成器更简单。 最后，列表模式添加了对模式匹配的更多支持。C# 10 版发布时间：2021 年 11 月C# 10 向 C# 语言添加了以下功能和增强功能：记录结构结构类型的改进内插字符串处理程序global using 指令文件范围的命名空间声明扩展属性模式对 Lambda 表达式的改进可使用 const 内插字符串记录类型可密封 ToString()改进型明确赋值在同一析构中可同时进行赋值和声明可在方法上使用 AsyncMethodBuilder 属性CallerArgumentExpression 属性增强的 #line pragma预览模式下提供了更多功能。 为了使用这些功能，需要在项目中将 ` 设置为 Preview`：本文后面的泛型属性。接口中的静态抽象成员。C# 10 继续致力于删除不必要的模式、将数据与算法分离以及提高 .NET 运行时的性能等主题。许多功能意味着可以通过键入更少的代码来表达相同的概念。 记录结构合并了许多记录类所使用的相同方法。 结构和匿名类型支持 with 表达式。 全局 using 指令和文件范围的命名空间声明意味着可以更清楚地表达依赖项和命名空间组织。 进行 Lambda 改进后，就可以在使用 Lambda 表达式时更容易地声明它们。 新的属性模式和析构改进可创建更简洁的代码。新的内插字符串处理程序和 AsyncMethodBuilder 行为可提高性能。 在 .NET 运行时中应用了这些语言功能来实现 .NET 6 中的性能改进。C# 10 还标志着每年 .NET 发布节奏的更多转变。 因为不是每项功能都可以在一年内完成，因此你可以尝试 C# 10 中的几项“预览”功能。 泛型属性和接口中的静态抽象成员均可使用，但这些预览功能在最终发布之前可能会发生更改。C# 9 版发布时间：2020 年 11 月C# 9 随 .NET 5 一起发布。 它是面向 .NET 5 版本的任何程序集的默认语言版本。 它包含以下新功能和增强功能：记录仅限 Init 的资源库顶级语句模式匹配增强：关系模式和逻辑模式性能和互操作性本机大小的整数函数指针禁止发出 localsinit 标志模块初始值设定项分部方法的新功能调整和完成功能目标类型的 new 表达式static 匿名函数目标类型的条件表达式协变返回类型扩展 GetEnumerator 支持 foreach 循环Lambda 弃元参数本地函数的属性C# 9 继续以前版本中的三大主题：删除不必要的模式、将数据与算法分离，以及在更多位置提供更多模式。顶级语句意味着主程序将更易于读取。 减少了不必要的模式：命名空间、Program 类和 static void Main() 都是不必要的。records 的引入为遵循值语义的引用类型提供了简洁的语法，以实现相等性。 使用这些类型来定义那些通常定义最小行为的数据容器。 仅限 init 的资源库提供了在记录中进行非破坏性修改的功能（with 表达式）。 C# 9 还…

C# 9.0新特性：记录(record)和with表达式
type: item
date: 2024-05-20T14:16:00+08:00
description: 在C#9.0中，引入了record，专门用来以最简的方式创建不可变类型的新方式。如果你需要一个行为像值类型的引用类型，你可以使用record；如果你需要整个对象都是不可变的，且行为像一个值，那么你也可考虑将其声明为一个record类型。 那么什么是record类型？1. Record介绍record类型是一种用record关键字声明的新的引用类型，与类不同的是，它是基于值相等而不是唯一的标识符——对象引用。他有着引用类型的支持大对象、继承、多态等特性，也有着结构的基于值相等的特性。可以说有着class和struct两者的优势，在一些情况下可以用以替代class和struct。提到不可变的类型，我们会想到readonly struct，那么为什么要选择添加一个新的类型，而不是用readonly struct呢？这是因为记录有着如下优点：在构造不可变的数据结构时，它的语法简单易用。record为引用类型，不用像值类型在传递时需要内存分配，并进行整体拷贝。构造函数和结构函数为一体的、简化的位置记录有力的相等性支持 —— 重写了Equals(object), IEquatable, 和GetHashCode()这些基本方法。2.1 record类型的定义与使用2.1.1 常规方式record类型可以定义为可变的，也可以是不可变的。现在，我们用record定义一个只有只读属性的Person类型如下。这种只有只读属性的类型，因为其在创建好之后，属性就不能再被修改，我们通常把这种类型叫做不可变类型。public record Person { public string LastName { get; } public string FirstName { get; } public Person(string first, string last) => (FirstName, LastName) = (first, last); }上面这种声明，在使用时，只能用带参的构造函数进行初始化。要创建一个record对象跟类没有什么区别：Person person = new("Andy", "Kang");如果要支持用对象初始化器进行初始化，则在属性中使用init关键字。这种形式，如果不需要用带参的构造函数进行初始化，可以不定义带参的构造函数，上面的Person可以改为下面形式。public record Person { public string? FirstName { get; init; } public string? LastName { get; init; } }现在，创建Person对象时，用初始化器进行初始化如下：Person person = new() { FirstName = "Andy", LastName = "Kang"};如果需要是可变类型的record，我们定义如下。这种因为有set访问器，所以它支持用对象初始化器进行初始化，如果你想用构造函数进行初始化，你可以添加自己的构造函数。public record Person { public string? FirstName { get; set; } public string? LastName { get; set; } }2.1.2 位置记录 / Positional records为了支持将record对象能解构成元组，我们给record添加解构函数Deconstruct。这种record就称为位置记录。下面代码定义的Person，记录的内容是通过构造函数的参数传入，并且通过位置解构函数提取出来。你完全可以在记录中定义你自己的构造和解构函数（注意不是析构函数）。如下所示：。public record Person { public string FirstName { get; init; } public string LastName { get; init; } public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName); public void Deconstruct(out string firstName, out string lastName) => (firstName, lastName) = (FirstName, LastName); }针对上面如此复杂的代码，C#9.0提供了更精简的语法表达上面同样的内容。需要注意的是，这种记录类型是不可变的。这也就是为什么有record默认是不可变的说法由来。public record Person(string FirstName, string …