当前位置: 首页 > news >正文

【层面一】C#语言基础和核心语法-03(泛型/集合/LINQ)

文章目录

  • 1.泛型(Generics)
    • 1.1 为什么需要泛型?
    • 1.2 泛型的救世主思维:“类型参数化”
    • 1.3 泛型的本质与原理
    • 1.4 如何使用泛型
    • 1.5 高级主题:协变与逆变
  • 2.集合与 LINQ
    • 2.1 集合-数据的容器
    • 2.2 LINQ-数据查询语言
  • 3.异步编程(Async/Await)
    • 3.1 核心痛点:为什么需要异步编程?
    • 3.2 核心比喻:异步编程就像点外卖
    • 3.3 异步编程的本质与原理
    • 3.4 如何正确地使用 Async/Await

1.泛型(Generics)

1.1 为什么需要泛型?

在泛型出现之前,我们主要用两种方式来编写“通用”的代码,但它们都有巨大的缺陷。

  1. 方式一:针对具体类型 - 重复的代码

假设我们需要一个能存放整数的盒子和一个能存放字符串的盒子:

public class IntBox
{public int Data { get; set; }
}public class StringBox
{public string Data { get; set; }
}// ... 还需要 BoolBox, PersonBox, 无穷无尽...

问题:代码重复。IntBox 和 StringBox 的逻辑完全一样,只是因为要存储的数据类型不同,我们就得写无数个几乎一模一样的类。这是维护的噩梦。

  1. 方式二:使用 object 类型 - 性能与安全性的灾难

为了解决重复问题,我们想到了 .NET 所有类型的基类:object。

public class ObjectBox
{public object Data { get; set; }
}// 使用
ObjectBox box1 = new ObjectBox();
box1.Data = 100; // 装箱(Boxing)发生!int 被包裹成 objectObjectBox box2 = new ObjectBox();
box2.Data = "Hello";// 取数据时,必须进行强制类型转换(Unboxing)
int myInt = (int)box1.Data; // 拆箱,正确
string myString = (string)box2.Data; // 正确// 但这是不安全的!
int crash = (int)box2.Data; // 编译通过,但运行时会抛出 InvalidCastException!

这种方式有两个致命缺点:

  1. 性能损耗:存储值类型(如 int)时会发生装箱(Boxing)拆箱(Unboxing) 操作,这是一个昂贵的性能开销。

  2. **类型不安全:**编译器无法检查强制类型转换是否有效,错误只能在运行时暴露,极易导致程序崩溃。

1.2 泛型的救世主思维:“类型参数化”

泛型的思路非常直观:既然逻辑是一样的,只是数据类型不同,那我们为什么不把‘类型’本身也作为一个参数呢?

就像是一个万能模具

  • 没有泛型:你需要为生产“塑料猫”、“塑料狗”、“塑料汽车”分别制造一个专用的模具。

  • 有了泛型:你只需要一个万能模具。使用时,你告诉它:“用塑料” -> 得到塑料猫;“用金属” -> 得到金属猫。“塑料”和“金属”就是类型参数

这个万能模具,就是泛型类。而“塑料”、“金属”,就是类型参数 T。

1.3 泛型的本质与原理

  1. 它是如何工作的?- 运行时支持

泛型并非 C# 语言的“语法糖”。它是 CLR(公共语言运行时)的内置功能,其本质是:在运行时根据需要动态生成具体的类型。

  • 当你定义泛型时:List<T>
    CLR 并不会立即创建一个真正的类型。它只是记住这个蓝图:“有一个列表,它的元素类型是 T”。

  • 当你使用泛型时

    List<int> intList = new List<int>();
    List<string> stringList = new List<string>();
    
    • CLR 遇到 List<int>:检查是否已生成过 int 版本的列表。如果没有,即时(JIT编译时) 地根据 List<T> 的蓝图,生成一个专用于 int 的具体类型。这个新类型中的 T 被全部替换为 int。
    • 同理,它为 List<string> 生成另一个专用于 string 的具体类型。

这个过程被称为“泛型类型实例化”。

  1. 它带来的三大核心优势

    1. 类型安全:编译器在编译时就能进行严格的类型检查。
    List<int> list = new List<int>();
    list.Add(100); // ✅ 正确
    list.Add("Hello"); // ❌ 编译错误!编译器直接报错,无法通过。
    int num = list[0]; // ✅ 直接就是 int,不需要强制转换。
    
    1. 性能卓越:彻底消除了装箱和拆箱。
    • List 内部就是一个 int [ ] 数组。

    • Dictionary<string, int> 内部就是 string 和 int 的键值对。

    • 值类型直接存储,引用类型直接存储引用,没有任何性能损耗。

    1. 代码复用:一份泛型代码,可以用于无限多种数据类型。.NET 标准库中的 List<T>, Dictionary<TKey, TValue>, Nullable<T> 等都是最好的例子,我们无需为自己定义的每一种类型都重写一遍集合类。

1.4 如何使用泛型

  1. 使用现有的泛型(消费者)
    这是我们最常做的角色。使用 System.Collections.Generic 命名空间下的泛型集合是入门第一步。
// 泛型列表
List<string> names = new List<string>();
names.Add("Alice");
names.Add("Bob");
string first = names[0]; // 类型安全,直接返回 string// 泛型字典
Dictionary<int, string> employeeMap = new Dictionary<int, string>();
employeeMap.Add(1, "Alice");
employeeMap.Add(2, "Bob");
string employee = employeeMap[1]; // 类型安全// 泛型方法(LINQ 中大量使用)
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Where<T> 和 ToList<T> 都是泛型方法,编译器通常能推断出 T 是 int
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
  1. 创建自己的泛型(生产者)

    1. 创建泛型类/接口/结构
      使用 <T> 语法来声明类型参数。

      // 定义一个自己的万能盒子
      public class MyBox<T> // T 是类型参数
      {public T Content { get; set; } // 使用 T 作为属性类型public bool IsContentEmpty(){// 我们可以把 T 当作一个已知类型来使用return Content == null || Content.Equals(default(T));}
      }// 使用
      MyBox<int> box1 = new MyBox<int> { Content = 100 };
      MyBox<string> box2 = new MyBox<string> { Content = "Hello" };
      MyBox<Person> box3 = new MyBox<Person> { Content = new Person() };
      
    2. 创建泛型方法
      即使类不是泛型的,方法也可以是泛型的。

      public class Utility
      {// 一个交换两个变量值的泛型方法public static void Swap<T>(ref T a, ref T b){T temp = a;a = b;b = temp;}
      }// 使用
      int x = 10, y = 20;
      Utility.Swap(ref x, ref y); // 编译器推断出 T 是 intstring s1 = "foo", s2 = "bar";
      Utility.Swap(ref s1, ref s2); // 编译器推断出 T 是 string
      
  2. 添加约束(让泛型更“懂事”)
    默认情况下,T 是 object 类型,你只能调用 object 的方法(如 ToString())。但我们可以通过 约束 来要求 T 必须满足某些条件,从而可以在泛型代码中调用更多特定的方法。

// 要求 T 必须是实现了 IComparable 接口的类型
// 这样我们就可以在方法里调用 a.CompareTo(b)
public T Max<T>(T a, T b) where T : IComparable<T>
{return a.CompareTo(b) > 0 ? a : b;
}// 使用
int maxInt = Max(10, 20); // ✅ int 实现了 IComparable<int>
string maxStr = Max("A", "B"); // ✅ string 实现了 IComparable<string>
// Person maxPerson = Max(p1, p2); // ❌ 如果 Person 没实现 IComparable<Person>,编译就会报错!

常用约束:

  • where T : struct:T 必须是值类型。

  • where T : class:T 必须是引用类型。

  • where T : new():T 必须有一个无参构造函数(允许 new T())。

  • where T : SomeBaseClass:T 必须继承自某个基类。

  • where T : ISomeInterface:T 必须实现某个接口。

1.5 高级主题:协变与逆变

这是泛型中更高级的概念,主要为了增加泛型接口和委托的灵活性。

  • 协变(Out):允许使用更派生的类型作为输出/返回值。

    • 用 out 关键字修饰类型参数(如 IEnumerable<out T>)。

    • IEnumerable<string> 可以被赋值给 IEnumerable<object>,因为 string 是 object 的派生类,且 IEnumerable 只“输出” T(通过 GetEnumerator()),是安全的。

  • 逆变(In):允许使用更基础的类型作为输入/参数。

    • 用 in 关键字修饰类型参数(如 Action<in T>)。

    • Action<object> 可以被赋值给 Action<string>,因为如果一个方法能处理任何 object,它必然能处理 string,是安全的。

简单理解:协变和逆变允许我们在保证类型安全的前提下,让泛型接口和委托的赋值操作变得更加灵活。


总结
.NET 泛型的本质是 CLR 支持的、在运行时动态生成具体类型 的机制。

  1. 它解决了:代码重复、类型不安全、性能损耗(装箱/拆箱)三大痛点。

  2. 它的核心是:将类型参数化,编写一份通用代码(“万能模具”),适用于多种具体类型(“塑料”、“金属”)。

  3. 它的优势是:类型安全、高性能、代码复用。

  4. 它的应用无处不在:从集合类 (List<T>) 到 LINQ,从依赖注入容器到异步编程 (Task<T>),泛型是现代 .NET 生态的基石。


2.集合与 LINQ

2.1 集合-数据的容器

  1. 核心概念:为什么需要集合?
    程序 = 数据结构 + 算法。绝大多数时候,我们都在处理一组数据,而不是单个数据。

    • 一个名字列表(List)

    • 一个商品和其价格的映射(Dictionary<Product, decimal>)

    • 一组唯一的用户ID(HashSet)

数组是简单的集合,但它一旦创建,大小就固定了,非常不灵活。.NET 集合框架提供了一系列功能丰富、灵活多变的数据结构,用于在各种场景下存储和操作一组对象。

  1. 集合的层次结构:接口的力量
    .NET 集合的强大之处在于其基于接口的设计。所有集合都实现了一组通用的接口,这使得它们在使用上具有一致性。

最核心的接口是 IEnumerable<T>:

  • 它只做一件事:暴露一个枚举器(IEnumerator<T>),该枚举器可以逐个返回集合中的元素。

  • 它的核心方法:GetEnumerator()。

  • 它的本质:它表示“我可以提供我的数据,让你能遍历我”。这是所有集合的基石,也是 LINQ 能够工作的前提

// IEnumerable<T> 的简化定义
public interface IEnumerable<out T>
{IEnumerator<T> GetEnumerator();
}public interface IEnumerator<out T>
{T Current { get; } // 获取当前元素bool MoveNext();   // 移动到下一个元素void Reset();      // 重置枚举器
}

foreach 循环的本质就是基于这两个接口:

// 这段 foreach 循环...
foreach (var item in myCollection)
{Console.WriteLine(item);
}// ...会被编译器翻译成类似这样的代码:
var enumerator = myCollection.GetEnumerator();
try
{while (enumerator.MoveNext()) // 只要还有下一个元素{var item = enumerator.Current; // 获取当前元素Console.WriteLine(item);}
}
finally
{enumerator.Dispose();
}

其他重要接口:

  • ICollection<T>:继承了 IEnumerable<T>,增加了 Count, Add, Remove, Clear 等功能。表示一个可修改的集合。

  • IList<T>:继承了 ICollection<T>,增加了通过索引访问的功能([index])。

  • IDictionary<TKey, TValue>:表示键值对的集合。

  1. 常用集合及其特性
集合类型主要接口核心特性与用途生活比喻
List<T>IList<T>动态数组。按索引访问极快(O(1))。在尾部添加元素快,在中间插入/删除慢(需移动后续元素)。最常用排队:可以快速找到第几个人(索引),但插队很麻烦。
Dictionary<TKey, TValue>IDictionary<TKey, TValue>哈希表。通过键(Key)查找值(Value)极快(O(1))。元素无序。键必须唯一。字典/电话簿:通过“名字”(Key)快速找到“电话号码”(Value)。
HashSet<T>ISet<T>不含重复值的集合。基于哈希实现,判断是否包含某元素极快(O(1))。用于去重或快速存在性检查。数学里的集合:{1, 2, 3},不允许重复元素。
Queue<T>IEnumerable<T>先进先出(FIFO) 的队列。Enqueue(入队),Dequeue(出队)。现实中的队列:先来的人先接受服务。
Stack<T>IEnumerable<T>后进先出(LIFO) 的栈。Push(压栈),Pop(弹栈)。一摞盘子:总是取最上面的那个,最后放上去的先被取走。
LinkedList<T>ICollection<T>双向链表。在任意位置插入/删除都很快(O(1)),但按索引访问慢(O(n))。寻宝游戏:每个线索指向下一个线索的位置。

选择指南:

  • 需要快速按索引访问? -> List<T>

  • 需要快速按键查找? -> Dictionary<TKey, TValue>

  • 需要确保元素不重复? -> HashSet<T>

  • 需要先进先出? -> Queue<T>

  • 需要后进先出? -> Stack<T>

2.2 LINQ-数据查询语言

  1. 核心概念:声明式编程
    想象一下,你想让助手从一堆文件中找出所有关于“财务”的PDF报告,按日期排序,并只要前5个。
  • 命令式编程(如何做):你会一步步指挥他:“打开文件夹,遍历每个文件,检查扩展名是不是.pdf,再打开文件检查内容是否包含‘财务’这个词,然后把符合的文件记下来,再根据文件修改日期排序,最后从排序好的列表里取前5个…” (繁琐,易错

  • 声明式编程(做什么):你会直接告诉他:“帮我找一下最新的5份财务PDF报告。” (简洁,直观

LINQ 就是 C# 中的声明式查询语言。你只需要告诉它你想要什么结果,而不需要关心它底层如何一步步去实现这个结果。

  1. LINQ 的两种语法风格

    1. 查询语法(Query Syntax)- 类似 SQL

      // 数据源
      var numbers = new List<int> { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };// 查询:从数字集合中找出小于5的偶数,并按大小排序
      var query = from num in numbers       // from 定义数据源和范围变量where num < 5            // where 过滤条件where num % 2 == 0       // 可以多个条件orderby num descending   // orderby 排序select num;              // select 选择结果foreach (var num in query)
      {Console.WriteLine(num); // 输出 4, 2, 0
      }
      

      这种语法可读性非常高,尤其对于复杂的多表连接查询。

    2. 方法语法(Method Syntax)- 基于扩展方法和 Lambda

      // 同样的查询,用方法语法实现
      var query = numbers.Where(num => num < 5)         // Where 扩展方法,传入Lambda表达式作为谓词.Where(num => num % 2 == 0).OrderByDescending(num => num) // OrderByDescending 扩展方法.Select(num => num);           // Select 扩展方法foreach (var num in query)
      {Console.WriteLine(num); // 输出 4, 2, 0
      }
      
  2. LINQ 的核心原理:延迟执行

这是 LINQ 最精妙也最重要的特性。

  • 定义查询 != 执行查询。当你写一连串的 .Where(), .OrderBy(), .Select() 时,你只是在构建一个查询计划,并没有真正开始遍历数据源。

  • 真正的执行时机是当你真正需要数据的时候,通常发生在:

    • 迭代查询结果(foreach)

    • 调用 ToList(), ToArray(), ToDictionary() 等方法

    • 调用 Count(), First(), Single(), Max() 等聚合方法

var numbers = new List<int> { 1, 2, 3, 4, 5 };
// 1. 定义查询(此时查询并未执行!)
var query = numbers.Where(n => {Console.WriteLine($"Checking {n}");return n % 2 == 0;
});Console.WriteLine("Query defined.");// 2. 现在开始执行查询!(触发点:ToList())
var evenNumbers = query.ToList(); 
// 输出:
// "Query defined."
// "Checking 1"
// "Checking 2"
// "Checking 3"
// "Checking 4"
// "Checking 5"// 如果再次执行查询,会再次遍历数据源
var count = query.Count(); // 再次输出 "Checking 1" ...

延迟执行的好处:

  1. 提高性能:可以组合多个查询操作,但最终只遍历数据源一次。

  2. 实时查询:如果数据源在查询定义后发生了改变,执行时会使用最新的数据。


  1. 常用 LINQ 操作符
  • 筛选:Where (找出满足条件的元素)

  • 投影:Select (将元素转换成另一种形式)

  • 排序:OrderBy, OrderByDescending, ThenBy

  • 分组:GroupBy (产生 IGrouping<TKey, TElement>)

  • 连接:Join, GroupJoin (像 SQL 里的 JOIN)

  • 聚合:Count, Sum, Average, Min, Max, Aggregate

  • 元素:First, FirstOrDefault, Single, Last

  • 集合:Distinct, Union, Intersect, Except

  • 转换:ToArray, ToList, ToDictionary, OfType<T>, Cast<T>


总结与关系

  • 集合是数据的容器:它们提供了存储和组织数据的不同数据结构(List, Dictionary, HashSet 等)。核心接口是 IEnumerable<T>,它允许遍历。

  • LINQ 是数据的查询工具:它提供了一套声明式的、统一的语法来查询任何实现了 IEnumerable<T> 的数据源(包括集合、数组、XML、数据库等)。

  • 它们的关系:LINQ 查询以集合为输入,并通常以另一种形式的集合为输出。你可以将多个 LINQ 操作符链式调用,形成一个复杂的查询管道,对原始集合进行过滤、排序、投影、分组等操作,最终得到你想要的结果。

最终比喻:

  • 集合就像原材料仓库(仓库里有货架、冷藏库、格子间等不同存储方式)。

  • LINQ 就像一位智能的仓库管理员。你只需要对他说:“帮我把仓库里所有过期日期大于下周的、产自山东的苹果,按价格从低到高排好,把它们的名字和价格列个表给我。” (声明式查询)

  • 管理员(LINQ)会自己去仓库(集合)里,按照你的要求(查询操作符)把东西找出来、处理好,然后交给你最终的结果(新的集合或单个值)。

3.异步编程(Async/Await)

3.1 核心痛点:为什么需要异步编程?

想象一个场景:你去一家快餐店点餐。

  • 同步编程(Synchronous):就像只有一个服务员。他收到你的点单后,自己跑到后厨去做汉堡,全程站在厨房里等待,直到汉堡做好后再回来为你服务。在这期间,他完全无法接待其他顾客。整个服务通道被一个耗时的任务完全阻塞

  • 异步编程(Asynchronous):就像一个有高效流程的餐厅。服务员收到你的点单后,将订单交给后厨,然后立即回来继续接待下一位顾客。后厨独立工作,当你的汉堡做好后,会通过叫号系统通知你来取(或者由另一个专人送来)。服务员(线程)的时间没有被浪费在等待上

在程序中,这个“做汉堡”的耗时任务通常是:

  • I/O 密集型操作:读写文件、网络请求(调用API、访问数据库)、下载文件。这些操作的特点是需要等待外部设备,CPU大部分时间在空闲等待。

  • CPU 密集型操作:复杂的计算、图像处理、加密解密。这些操作的特点是CPU满负荷工作

异步编程的核心目标就是:在等待耗时操作(尤其是I/O操作)完成的过程中,释放当前线程,让它去处理其他工作,从而最大限度地提高应用程序的吞吐量和响应能力。


3.2 核心比喻:异步编程就像点外卖

让我们用一个更贴切的比喻来理解 async/await 的关键角色:

  1. 你(调用方):想吃外卖。

  2. async 方法(餐厅):你打电话下单的餐厅。餐厅接单后,给你一个订单号(Task<T>),代表一个“未来的餐食”(Future)。

  3. await 关键字(等餐&做其他事):你不会傻傻地站在门口等外卖员来(阻塞)。你可以:

    • A. 真正异步(推荐):放下电话(释放线程)去看电视。外卖到了(操作完成),门铃响起(回调),你再去拿。

    • B. 伪异步(错误):.Result 或 .Wait() 就像你站在门口死死盯着马路,不让任何人进出(阻塞线程),直到外卖送到。这完全失去了点外卖的意义。

在这个比喻中:

  • async:修饰方法,声明“我这个方法内部包含异步操作,调用我会返回一个 Task(订单号)”。

  • await:用在异步操作前,意思是“等到这个异步操作完成,但在此期间,请释放当前线程回去干别的事”。

Task / Task<T>:代表一个异步操作,是那个“订单号”或“未来的结果”。Task 是无返回值的订单,Task<int> 是未来会有一个 int 结果的订单。


3.3 异步编程的本质与原理

  1. 状态机(State Machine):编译器的魔法
    async/await 最大的魔力在于,它让你用写同步代码的思维方式(从上到下顺序执行)来写异步代码,但其底层完全是由编译器重构的。

当你写一个 async 方法时:

public async Task<string> GetHtmlAsync(string url)
{var client = new HttpClient();string html = await client.GetStringAsync(url); // <- 暂停点return html.ToUpper();
}

C# 编译器会做以下事情:

  1. 将方法拆解:它将你的方法拆分成多个片段,以 await 为界限。await 之前的代码是第一部分,await 之后的代码是第二部分。

  2. 生成一个状态机类:编译器会为你生成一个隐藏的、复杂的类(状态机)。这个类会记住方法的执行状态(比如局部变量的值)和当前执行到了哪个片段(比如是在 await 之前还是之后)。

  3. 处理 await:当执行到 await 时,状态机:

    • 启动异步操作(如 client.GetStringAsync(url))。

    • 立即返回一个 Task 给调用者

    • 订阅异步操作的完成回调

    • 然后!它就释放当前线程了!(如果是UI线程,它就回去处理点击事件;如果是线程池线程,它就回去处理其他请求)。

  4. 完成后恢复:当异步操作(网络下载)完成时,它的完成回调会通知状态机。状态机会抓取一个空闲的线程(可能是原来的线程,也可能是另一个新线程),然后从它上次离开的地方(await 之后)继续执行剩下的代码。

所以,async/await 的本质是编译器提供的“语法糖”,它自动为你构建了一个复杂的状态机,来处理异步操作的启动、挂起和恢复,让你无需手动处理繁琐的回调。

  1. 线程 vs. 异步:关键区别

这是最大的误解!异步 != 多线程。

  • 多线程:是关于使用多个CPU核心( worker)来同时执行多个计算任务(CPU密集型)。

  • 异步:是关于避免线程被阻塞( I/O密集型),在等待时释放线程。

一个异步操作可能根本不占用任何线程!
在等待I/O操作(如网络请求、磁盘读写)时,是硬件设备(网卡、磁盘控制器)在工作,不需要CPU线程。.NET 运行时利用操作系统提供的 I/O 完成端口 等机制,在硬件工作完成后才通知运行时,再由运行时安排一个线程来处理后续逻辑。

步编程的核心价值在于:用极少的线程(甚至是1个线程,如UI线程)处理大量并发的I/O操作。


3.4 如何正确地使用 Async/Await

  1. 黄金法则
  • Async All the Way:异步调用应该像病毒一样传播。如果一个方法是 async 的,那么调用它的方法也最好 async,一直延伸到顶层(如事件处理函数)。不要混合同步和异步。

  • 避免使用 Task.Wait() 或 Task.Result:这在绝大多数情况下会导致死锁(尤其是在UI线程或ASP.NET旧版本中),因为它强制异步操作同步完成,阻塞了线程。

  • 使用 ConfigureAwait(false):在库代码或非UI上下文中,如果你不关心后续代码在哪个原始上下文中恢复,使用 await task.ConfigureAwait(false)。这可以提高性能并避免潜在的死锁。但在UI应用中(如按钮点击事件里),你通常需要回到UI线程来更新UI,所以不能使用它。

  1. 代码示例对比
    错误示范(同步阻塞,浪费线程):
// 在Web服务器中,这会阻塞一个宝贵的线程池线程
public string GetData()
{var client = new HttpClient();// .Result 会阻塞当前线程,直到下载完成string result = client.GetStringAsync("https://api.example.com/data").Result;return result;
}

正确示范(异步非阻塞,释放线程):

// async ALL THE WAY!
public async Task<string> GetDataAsync()
{var client = new HttpClient();// await 会释放当前线程,去处理其他请求string result = await client.GetStringAsync("https://api.example.com/data");return result;
}// 在ASP.NET Core Controller中调用
[HttpGet]
public async Task<IActionResult> Index()
{var data = await GetDataAsync(); // 这里也会释放请求线程!return View(data);
}

在ASP.NET Core中,当一个请求线程在 await 时被释放,它可以立即回去处理另一个新进来的请求。当异步操作完成后,任何空闲的线程池线程都可以接手继续处理后续工作。这使得服务器可以用很少的线程处理非常高的并发请求

  1. 异常处理
    异步方法的异常会被捕获并存储在返回的 Task 对象中。当你 await 这个 Task 时,异常会被重新抛出
try
{await SomeAsyncMethodThatMightFail();
}
catch (Exception ex)
{// 在这里捕获异步方法中抛出的异常Console.WriteLine(ex.Message);
}

总结

  1. 为什么需要:解决I/O操作中的线程阻塞问题,极大提升应用程序的吞吐量响应性

  2. 本质是什么:是编译器提供的语法糖,其底层通过生成状态机来自动化异步操作的启动、挂起和恢复流程。

  3. 核心机制:await 关键字是“暂停点”,它会立即返回一个 Task,并在异步操作完成前释放当前线程

  4. 关键区别:异步是关于I/O等待,线程是关于CPU计算。异步操作在等待期间可能不占用任何线程。

  5. 最佳实践:Async All the Way,避免 .Result/.Wait(),在库代码中考虑使用 ConfigureAwait(false)。


文章转载自:

http://KIKwzpuC.mtzyr.cn
http://dWKq3H0Q.mtzyr.cn
http://UI9fUzsF.mtzyr.cn
http://7FBGRUFC.mtzyr.cn
http://RxuFHA2o.mtzyr.cn
http://8tXO4V39.mtzyr.cn
http://tusHTWl6.mtzyr.cn
http://jSbcCdb6.mtzyr.cn
http://MvrQHN3l.mtzyr.cn
http://4vzB1asQ.mtzyr.cn
http://CG9C7wVV.mtzyr.cn
http://knQBmTev.mtzyr.cn
http://QRfwrHbI.mtzyr.cn
http://s98qJIHt.mtzyr.cn
http://inUKYQfc.mtzyr.cn
http://mgmCWlhI.mtzyr.cn
http://JhccvoBb.mtzyr.cn
http://iC3SXSs7.mtzyr.cn
http://zL9JANK8.mtzyr.cn
http://tA5FBAC1.mtzyr.cn
http://LLMU82JU.mtzyr.cn
http://ky3GHOPy.mtzyr.cn
http://zIo5SRM1.mtzyr.cn
http://qv90fPT7.mtzyr.cn
http://kqXlM3RS.mtzyr.cn
http://AWyGlekk.mtzyr.cn
http://G7ggcgvw.mtzyr.cn
http://w1zL0XoF.mtzyr.cn
http://sCaCmF1e.mtzyr.cn
http://uMJOgHVt.mtzyr.cn
http://www.dtcms.com/a/384470.html

相关文章:

  • 【连载4】 C# MVC 环境差异化配置:异常处理策略
  • 计算机视觉进阶教学之背景建模与光流估计
  • 铝锆中间合金市场报告:深度解析与未来趋势展望
  • 数据库事务:ACID
  • 动态电源路径管理(DPPM)、NVDC动态路径管理
  • 深入理解链表:从基础概念到经典算法
  • 手写MyBatis第60弹: 如何优雅处理各种参数类型,从ParamNameResolver到TypeHandler
  • 【Postman】Postman 自动化测试指南:Token 获取与变量管理实战
  • Java 大视界 -- 基于 Java 的大数据可视化在城市交通拥堵治理与出行效率提升中的应用
  • arcgis中实现四色/五色法制图
  • OpenVLA: An Open-Source Vision-Language-Action Model
  • nvm安装node后出现报错: “npm 不是内部或外部命令,也不是可运行的程序 或批处理文件”
  • iPhone 17 系列与 iPhone Air 对比:硬件
  • Serverless Redis实战:阿里云Tair与AWS MemoryDB深度对比
  • 欢迎来到std::shared_ptr的派对!
  • 计算机操作系统学习(四、文件管理)
  • Open3D-Geometry-15:UV Maps 将2D图像投影到3D模型表面
  • 从pip到UV:新一代包管理器的高效替代方案
  • 基于Matlab的雾霾天气和夜间车牌识别系统
  • 【Unity】高性能的事件分发系统
  • BM3D 图像降噪快速算法的 MATLAB 实现
  • 【pycharm】 ubuntu24.04 搭建uv环境
  • 科普:Python 的包管理工具:uv 与 pip
  • Golang语言入门篇002_安装Golang
  • cemu运行塞尔达传说:旷野之息的闪退问题以及解决方案记录
  • 【面试之Redis篇】主从复制原理
  • MySQL 8.0 在 Ubuntu 22.04 中如何将启用方式改为mysql_native_password(密码认证)
  • 轨道交通绝缘监测—轨道交通安全的隐形防线
  • Golang 语言中的函数类型
  • 《投资-54》数字资产的形式有哪些?