《深入解析 C#》—— C# 3 部分

文章目录

    • 第三章 C#3:LINQ及相关特性
      • 3.1 自动实现属性(*)
      • 3.2 隐式类型 var(*)
      • 3.3 对象和集合初始化
        • 3.3.1 对象初始化器
        • 3.3.2 集合初始化器
      • 3.4 匿名类型
        • 3.4.1 基本语法和行为
        • 3.4.2 编译器生成类型
        • 3.4.3 匿名类型的局限性
      • 3.5 lambda 表达式
        • 3.5.1 捕获变量
        • 3.5.2 表达式树
      • 3.6 扩展方法
        • 3.6.1 声明扩展方法
        • 3.6.2 调用扩展方法
        • 3.6.3 扩展方法的链式调用
      • 3.7 查询表达式
        • 3.7.1 从 C# 到 C# 的查询表达式转换
        • 3.7.2 范围变量和隐形标识符
        • 3.7.3 选择使用哪种 LINQ 语法
      • 3.8 终极形态:LINQ

第三章 C#3:LINQ及相关特性

3.1 自动实现属性(*)

3.2 隐式类型 var(*)

3.3 对象和集合初始化

3.3.1 对象初始化器
image-20240319092546110

​ 对象初始化器的作用只是表达应该如何初始化每个属性。

​ 注意,只有在使用对象初始化器或者集合初始化器时,构造器的参数列表才再以省略。

  • 如果初始化值(“=” 右边的内容)是一个普通的表达式,那么会先计算该表达式的值,然后将结果传给属性对应的 set 访问器。
  • 如果初始化值是另一个对象初始化器,则不会调用 set 访问器,而会调用 get 访问器,然后将嵌套对象初始化器得到的结果应用于由 get 访问器返回的属性。
image-20240319092607335

​ 上述代码等同于以下代码:

image-20240319092623377
3.3.2 集合初始化器

​ 集合初始化器多用于创建新集合。下面这行代码创建了一个字符串集合并为其添加初始值:

image-20240319092730747

​ 编译器会将以上代码转换成一个构造器调用,其后紧跟一系列 Add 方法的调用:

image-20240319092750355

​ 对于 Dictionary<TKey, TValue>,添加元素的方法是 Add (key, value):

image-20240319092911075

​ 编译器把每个元素初始化器都看作一个 Add 调用。如果元素初始化器没有大括号,则将其作为单个参数传递给 Add 方法。上述字典的例子等同于如下代码:

image-20240319093017901 image-20240319093005951

​ 只有实现了 IEnumerable 接口的类型才能够使用集合初始化器。

3.4 匿名类型

3.4.1 基本语法和行为

​ 使用匿名类型可以更精练地表达“一次性”的类型需求,同时还不失静态类型的优势:

image-20240319093320358
  1. 匿名类型的语法类似于对象初始化器,但无须指定类型名称,只需要 new 关键字、左大括号、属性以及右大括号。这一形式称为匿名对象创建表达式

  2. 声明 player 变量使用了 var 关键字,因为所创建的类型是匿名类型,所以只能用 var 来声明(也可以使用 object 来声明,不过意义不大)。

  3. 以上代码依然属于静态类型的范畴。Visual Studio 会为 player 变量自动设置 Name 和 Score 属性。如果要访问一个不存在的属性(比如 player.Points),则编译器会报错。

  4. 属性的类型是根据赋值的类型进行推断的:player.Name 是 string 类型,player. Score 是 int 类型。

投射初始化器:

​ 可以从其他对象复制属性或字段到新对象中,并且二者的属性或字段名称相同。

image-20240319093835592

​ 上述例子中,除了 CustomerName,其他属性都使用了投射初始化器。以上代码的运行结果和下面这种显式写出每个属性名称得到的结果是相同的:

image-20240319093932139

​ 如果目标属性或字段的名称与源名称一致,那么可以交由编译器来推断名称,如以下代码:

image-20240319094354105

​ 可以直接简化为:

image-20240319094406624

说明:

​ 尽管以上两种形式的代码结果相同,但不是所有行为都相同。

​ 例如,在项目中将 Address 属性重命名为 CustomerAddress,若使用投射初始化器,那么 flattenedItem.Address 也将变为 flattenedItem.CustomerAddress。

3.4.2 编译器生成类型

​ 虽然源码中没有出现匿名类型的名称,但编译器需要为它生成一个类型。

  • 它在执行期没有任何特殊之处,对于执行期来说也只是一个普通的类型而已。
  • 该类型的名称不是一个有效的 C# 名称。

​ 关于该类型,还有几个比较有意思的特征(其中一些得到了语言规范层面的保证):

  1. 它是一个类(保证)。
  2. 其基类是 object (保证)。
  3. 该类是密封的(不保证,虽然非密封的类并没有什么优势)。
  4. 属性是只读的(保证)。
  5. 构造器的参数名称与属性名称保持一致(不保证,有时对于反射有用)。
  6. 对于程序集是 internal 的(不保证,在处理动态类型时会比较棘手)。
  7. 该类会覆盖 GetHashCode() 和Equals() 方法:两个匿名类型只有在所有属性都等价的情况下才等价(可以正常处理 null 值)。只保证会覆盖这两个方法,但不保证散列值的计算方式。
  8. 覆盖并完善 ToString() 方法,用于呈现各属性名称及其对应值。这一点不保证,但对于问题诊断来说作用重大。
  9. 该类型为泛型类,其类型形参会应用于每一个属性。具有相同属性名称但属性类型不同的匿名类型,会使用相同的泛型类型,但拥有不同的类型实参。这一点不保证,不同编译器的实现方式不同。
  10. 如果两个匿名对象创建表达式使用相同的属性名称,具有相同的属性类型以及属性顺序, 并且在同一个程序集中,那么这两个对象的类型相同。

​ 可以利用第 10 点使用匿名类型来创建隐式类型数组:

image-20240320122229641
3.4.3 匿名类型的局限性
  1. 难以应用于方法签名中。即, 难以在多处使用同一个匿名类型。
  2. 匿名类型不提供任何数据封装。即,匿名类型中不能有校验,也不能添加任何行为。

3.5 lambda 表达式

3.5.1 捕获变量

​ 给出如下设计好的代码示例:

image-20240320122846091
  • instanceField 是 CapturedVariablesDemo 类的一个实例字段,被 lambda 表达式所捕获。
  • methodParameter 是 CreateAction 方法的一个参数,被 lambda 表达式所捕获。
  • methodLocal 是 CreateAction 方法中的一个局部变量,被 lambda 表达式所捕获。
  • uncaptured 是 CreateAction 方法中的一个局部变量,因为没有被 lambda 表达式使用,所以不属于捕获变量。
  • lambdaParameter 是 lambda 表达式自己的参数,不属于捕获变量。
  • lambdaLocal 是 lambda 表达式内部的局部变量,不属于捕获变量。

通过生成类来实现捕获变量

  • 没有捕获任何变量,编译器会创建一个静态方法,不需要额外的上下文。
  • 仅捕获了实例字段,编译器会创建一个实例方法。实例字段的捕获数目没有影响,只需要一个 this 便都可以访问。
  • 捕获了局部变量或者参数,编译器会创建一个私有的嵌套类用于保存上下文信息,在该类中创建一个实例方法用于容纳原 lambda 表达式内容,并使用嵌套类来访问捕获变量。

​ 应用上述规则,编译器转义后的代码如下:

image-20240320123817033

说明:

​ 具体实现细节因编译器而异。例如对于没有捕获变量的 lambda 表达式,编译器可能会创建一个包含一个实例方法的嵌套类,而不是创建一个静态方法。委托的执行效率会因创建方式的不同而略有差异。这里只描述编译器为访问捕获变量所做的那些必要、基本的工作, 其复杂度可能根据实际需要而增加。

局部变量的多次实例化

​ 简单起见,下列代码不捕获参数和实例字段,只捕获一个局部变量:

image-20240320124047278

​ 在这段代码中,每次声明 text 时,该变量就完成一次实例化,因此每个 lambda 表达式捕获的都是不同的变量实例,于是 5 个完全独立的 text 变量被分别捕获。虽然这段代码中变量初始化后没有任何修改操作, 但编译器的做法是:每次初始化都创建一个不同的生成类型实例。编译器转义后的代码如下:

image-20240320124243481

多个作用域下的变量捕获

​ 循环的每次迭代都要实例化一次变量,是因为变量作用域的缘故。一个方法内部可能存在多个作用域,每个作用域都可能包含局部变量的声明,而一个 lambda 表达式可以从多个作用域捕获变量,给出如下示例代码:

image-20240320124442728

​ 其执行结果如下:

image-20240320124454985

​ 其中,outercounter 变量被两个委托共用,而 innerCounter 为画个委托分别所有。每个委托都需要各自的上下文,但是各自的上下文还需要指向一个公共的上下文。编译器会为这种情况创建两个私有嵌套类,转义后的结果如下:

image-20240320124554001

​ 大多数情况很少需要查看这样的代码,但编译器生成代码的方式会对程序性能有不小的影响。如果在性能敏感的代码中使用 lambda 表达式,那么需要注意可能会因为变量捕获而创建过多对象,从而影响性能

3.5.2 表达式树

​ lambda 表达式可以由编译器转换成表达式树。表达式树是将代码按照数据来表示的一种形式。这项特性是 LINQ 能够有效处理 SQL 数据库的核心秘诀所在。通过表达式树,C# 的代码可以在执行期被分析并转换成 SQL。 委托的作用是提供可运行的代码,而表达式树的作用是提供可查看的代码(这有点类似于反射机制)。虽然也可以在代码中直接构建表达式树,但更普遍的做法是让编译器负责把 lambda 表达式转换成表达式树。

​ 以下面的 lambda 表达式为例:

image-20240320133626815

​ 编译器并未在任何地方生成一个硬编码的字符串。以上字符串是通过表达式树动态构建出来的。这段代码表明:代码是可以进行执行期检查的。这就是表达式树的所有关键所在。

​ 首先看 adder 的类型:Expression<Func<int, int, int>>。把它拆解成两部分: Expression<TDelegate> 和 Func<int, int, int>。Func<int, int, int> 是 Expression<TDelegate> 的类型实参,它是一个代理类型,由两个 int 参数和一个 int 返回值构成。

​ Expression<TDelegate>是处理 TDelegate 类型的表达式树类型。其中 TDelegate 必须是委托类型。委托类型仅仅是表达式树相关的诸多类型之一,它们均位于 Systarn.Linq.Expressions 命名空间下。非泛型的 Expression 类是所有表达式类型的抽象基类。

​ adder 变量是一个表示接收两个整型值并返回一个整型值方法的表达式树表示,之后可以用 lambda 表达式来为该变量赋值。编译器负责生成适用于执行期的表达式树。示例代码如下:

image-20240321102700117 image-20240321102713913

转换表达式树的局限性

​ 只有拥有表达式主体的 lambda 表达式才能转换成表达式树。下面这句代码会编译报错:

image-20240321103154119
  • 从 .NET 3.5 开始,表达式树 API 就已经扩展支持代码块和其他构建了,但 C# 编译器依然保留了该限制,而且对于 LINQ 使用的表达式树也有同样的限制。
  • 这是对象初始化器和集合初始化器很重要的原因:可以在一个表达式内完成初始化,以供表达式树使用。
  • 另外,lambda 表达式不能使用赋值运算符,也不能使用 C# 4 的动态类型和 C# 5 的异步。

将表达式树编译成委托

​ 表达式树可用于在执行期动态构建委托。这种方式一般需要手动编写部分代码,而不是使用 lambda 表达式进行转化。

​ Expression<TDelegate> 有一个 Compile() 方法,该方法返回一个委托类型。该委托类型与普通的委托类型无异。

​ 以上述代码为例,构建出 adder 表达式树,将其编译成一个委托,然后调用该委托并打印出结果:

image-20240321103549075

3.6 扩展方法

3.6.1 声明扩展方法
  • 扩展方法必须声明在一个非嵌套、非泛型的静态类中。
  • 在 C#7.2 之前箕一个参数不能是 ref 参数。
  • 扩展方法所在的类不能是泛型类,但扩展方法自身可以是泛型方法。
  • 扩展方法的第一个参数有时称为扩展目标扩展类型
image-20240321103750423

​ 编译器唯一需要做的就是为扩展方法及其所在类添加[Extension]特性。该特性在命名空间 System.Runtime.CompilerServices 下。其本质上是一个标记,标记 ToInstant() 方法可以按照 DateTimeOffset 的实例方法那样凋用。

3.6.2 调用扩展方法

​ 扩展方法可以在其第一个参数的类型实例上以实例方法的调用方式进行调用,但还需要一个前提:让编译器可以查找到这个扩展方法。

优先级问题

  1. 如果存在一个与该类同名的普通实例方法,那么编译器总是会优先选择该实例方法来调用。

    • 在此过程中,无所谓扩展方法是否具有更匹配的形参。如果编译器查找到有可调用的实例方法,就不会再去查找扩展方法了。
  2. 如果编译器没有找到可调用的实例方法,那么会开始查找扩展方法。首先查找扩展方法调用代码所在的命名空间以及所有 using 指令指定的命名空间。

image-20240321104119590

​ 编译器会从以下位置查找扩展方法:

  • CSharpInDepth.Chapter03 命名空间下的静态类;
  • CSharpInDepth 命名空间下的静态类;
  • 全局命名空间下的静态类;
  • using 指令指定的命名空间下的静态类(例如 using System 这样的指向命名空间的命令);
  • (只在 C#6 中)using static 指定的静态类,10.1节还会介绍。

​ 补充:

  1. 编译器会从最内层的命名空间一路向外查找至全局命名空间。在查找的每条路径上,都要查找当前命名空间下的静态类,或者查找 using 指令指定的命名空间中的类。
  2. 查找的顺序并不重要。如果调整 using 指令的顺序后影响了扩展方法的查找结果,建议将扩展方法重新命名。
  3. 查找的每一步中都有可能找到多个适合调用的扩展方法。此时编译器会对当前所有候选方法执行常规的重载决议。
  4. 在决策完成与,编译器为调用扩展方法所生成的 IL 代码和调用普通静态方法所生成的 IL 代码是完全相同的。

说明:

​ x.Method(y);

​ 如果 Method 是实例方法,x 为 null,就会抛出 NulLReferenceException;

​ 而如果 Method 是一个扩展方法,那么即便 x 为 null,也会将 x 作为其首个参数进行方法调用。

3.6.3 扩展方法的链式调用

​ 下面示例代码是一个简单查询:现有一个单词序列,按照单词长度进行筛选,并将其按字母顺序排序,然后全部转换为大写。该查询只用到了 C#3 中的 lambda 表达式和扩展方法这两个特性。

image-20240321104753619

​ 注意:以上代码中 Where、OrderBy 和 Select 三个调用的顺序就是操作实际发生的顺序。由于 LINQ 中存在延退和优化策略,很难知道具体何时会执行什么操作,但代码的阅读顺序和执行顺序是一致的。

​ 下列代码实现了上述相同的查询功能,但没有使用扩展方法。

image-20240321104901442

​ 对比之下可以发现明显的缺陷:代码阅读起来很困难。代码中方法调用的顺序和实际执行的顺序刚好相反:Where 方法是第一个被调用的,却放在了末尾。lambda 表达式 word => word.ToUpper() 究竟属于哪个方法调用很不明确。它本属于 Select 方法, 但和 Select 中间隔了一堆代码。

​ 还有一个解决方法是将每个方法调用的结果都赋给一个局部变量,然后通过上一个变量再继续调用下一个方法。但大量额外的局部变量容易造成混淆且会分散注意力。

image-20240321105153037

​ 由上可见,方法的链式调用带来的好处不仅仅限于 LINQ。一个方法调用的结果用作另一个方法调用的开始。扩展方法能让我们以可读性强的方式编码任何类型,而且不局限于那些已经支持链式调用的类型。

3.7 查询表达式

​ 虽然几乎 C# 3 的所有特性都对 LINQ 有所贡献,但只有查询表达式是专门为 LINQ 设计的。 使用查询表达式,我们可以通过查询专用语句(select, where、let、group by 等)编写简洁的查询代码。由编译器负责把查询表达式翻译成非查询语句的形式,并进行常规编译。回顾一下 3.6.3 节的代码:

image-20240321105448250

​ 使用查询表达式改写的功能相同的查询代码如下所示,其中加粗的部分为查询表达式:

image-20240321105515218
3.7.1 从 C# 到 C# 的查询表达式转换

​ 语言规范直接将查询表达式定义为一种语法转译,且该转译过程发生在绑定或重载决议之前。即,查询表达式会首先被编译器转义为可执行的 C# 代码。很多时候,转译的结果就是使其变成对应的扩展方法调用,不过语言规范并没有强制要求该行为。

3.7.2 范围变量和隐形标识符

​ 查询表达式引入了范围变量的概念。范围变量与普通变量不同,范围变量充当了查询语句中每条子句中的输入。

​ 在上一个例子中,位于查询表达式起始位置的 from 子句引入了范围变量(加粗部分):

image-20240321110014925

​ 子句中引入范围变量的最简单方式应该是使用 let 关键字。假设需要在查询中多次使用单词长度这个变量,但又不想每次都调用 Length 属性。如果需要就单词长度进行琲序,并且在输出结果中使用长度变量,那么使用 let 子句的查询如下所示:

image-20240321110103716

​ 在对查询进行转译时,该如何表示 length 和 word 呢?这需要把原始的单词序列转换成“单词-长度”对。在需要访问范围变量的子句中,再通过变量对来访问其中的某个变量:

image-20240321110336455

​ 这里的 tmp 不属于查询转译的一部分,语言规范中是用 * 符号表示的。在语言规范并没有规定为查询构建表达式树时,参数应当使用什么名称。这个名称本身不重要,因为在编写查询时它是不可见的,因此把它称为隐形标识符

3.7.3 选择使用哪种 LINQ 语法
  • 查询表达式:更适合大规模查询,表现出众,可读性强。
    • 必须以 from 子句开始,以 select 或者 group by 子句结尾。
  • 方法语法:更适合简单查询,简单明了。

​ 例如:

image-20240321110831184

​ 对比采用扩展方法的写法,就显得有些笨拙了:

image-20240321110927691

说明:

​ 对于采用非查询表达式的语法,目前没有统一的术语,而有方法语法、点式语法、流式语法、lambda 语法等名称,之后会统一采用方法语法来代称。

​ 当查询变得更复杂时,方法语法依然可以从容应对:

  1. LINQ 中提供的很多方法,并没有与之对应的查询表达式语法。

    • 例如 Select 和 Where 的某些重载方法,返回的是元素以及元素对应的索引值。
  2. 如果想在查词的结尾执行一个方法调用(例如调用 ToList() 来把结果转换成 List<T> 对象),就要把整个查询表达式用圆括号括起来;

    如果使用方法语法,只需在末尾直接添加方法调用即可。

​ 在很多情况下(包括上述例子在内),两种方式难分高下。

3.8 终极形态:LINQ

​ 下面介绍 C#3 特性是如何成就 LINQ 的。假设有一个查询从 Entity Framework 获取数据,代码如下所示(假设已存在某数据库和相应的表结构):

image-20240321111400222

​ 短短 4 行代码,应用了所有新特性。

  1. 匿名类型.

    包括投射初始化器(只选择 name 和 price 这两个属性)。

  2. 使用 var 声明的匿名类型.

    因为无法声明 products 变量的有效类型。

  3. 查询表达式。

    当然对于本例可以不使用查询表达式,但对于更复杂的情况,使用查询表达式能事半功倍。

  4. lambda 表达式。

    lambda 表达式在这里作为查询表达式转译之后的结果。

  5. 扩展方法。

    使得转译后的查询可以通过 Queryable 类实现,因为 dbContext.Products 实现了 IQueryable<Product>接口。

  6. 表达式树。

    使得查询逻辑可以按照数据的方式传给 LINQ 提供器,然后转换成 SQL 语句并交由数据库执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/477346.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux信号补充——信号捕捉处理

一、信号的捕捉处理 ​ 信号保存后会在合适的时间进行处理&#xff1b; 1.1信号处理时间 ​ 进程会在操作系统的调度下处理信号&#xff0c;操作系统只管发信号&#xff0c;即信号处理是由进程完成的&#xff1b; ​ 1.信号处理首先进程得检查是否有信号&#xff1b;2.进程…

双指针(对撞指针、快慢指针)

本博客将讲述OJ题中的常用的双指针 双指针的含义 双指针算法是一种常用的算法技巧&#xff0c;它通常用于在数组或字符串中进行快速查找、匹配、排序或移动操作。 双指针并非真的用指针实现&#xff0c;一般用两个变量来表示下标&#xff08;在后面都用指针来表示)。双指针算…

QML TextField 默认无法鼠标选中内容

1.import QtQuick.Controls 2.0 后的TextField默认无法选中内容如下图&#xff1a; 2.增加属性设置 selectByMouse: true 可以选中内容了 TextField{ selectByMouse: true text:"1234567890987654321" } 效果如下:

多线程(JUC, ReentrantLock, 原子类, 线程池, 信号量 Semaphore, CountDownLatch)

JUC Java.util.concurrent 包, 存放了并发编程相关的组件, 目的是更好的支持高并发任务 (多线程只是实现并发编程的一种具体方式 …) ReentrantLock 可重入互斥锁, 和 synchronized 定位类似, 用来实现互斥效果, 保证线程安全. synchronized 对对象加锁, 保护临界资源Reentreat…

面向量产!基于视觉的速度距离估计

面向量产&#xff01;基于视觉的速度距离估计 论文名称&#xff1a;Vision-based Vehicle Speed Estimation: A Survey 导读 在精确检测车速车距的方案中&#xff0c;视觉方案是非常具有挑战性的&#xff0c;但由于没有昂贵的距离传感器而大幅降低成本&#xff0c;所以潜力巨…

【现代C++】范围基于的for循环

现代C中的范围基于的for循环&#xff08;range-based for loop&#xff09;是C11引入的一项特性&#xff0c;旨在简化对容器或范围的迭代过程。这种循环语法不仅使代码更清晰易读&#xff0c;还减少了迭代时的错误。以下是范围基于的for循环的详细介绍&#xff1a; 1. 基本用法…

Vue3的与2的简单区别

Vue2选项式api Vue3组合式API setup方法的使用&#xff0c;最后需要return setup语法糖省略了内部的export default{} 和return 内容 以及组件的注册 reactive生成响应式对象&#xff0c;只能适用于复杂对象&#xff0c;简单类型不可 ref生成响应式数据&#xff1a;复杂类型和简…

leetcode 数组练习,美团优选面试题java

public int maxSubArray(int[] nums) { int countnums[0]; int resnums[0]; for(int i1;i<nums.length;i){ if(count<0){ countnums[i]; }else{ countnums[i]; } resMath.max(res,count); } return res; } 3、两数之和 利用map,来存储数组值和当前位置&…

【Review】电动汽车百人会

汽车强国靠四化--电动化、智能化、低碳化、全球化。 1.坚持电动化&#xff1a;电动化是经过二十多年反复论证的既定战略和技术路线、不能动摇、无需改变、要将电动化进行到底&#xff0c;全力攻克下一代电动化核心技术--全固态锂电池;市场方面要采用“双轮”驱动战略一方面继续…

基于PID控制器的四旋翼无人机控制系统的simulink建模与仿真,并输出虚拟现实动画

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1四旋翼无人机的动力学模型 4.2 PID控制器设计 4.3 姿态控制实现 4.4 VR虚拟现实动画展示 5.完整工程文件 1.课题概述 基于PID控制器的四旋翼无人机控制系统的simulink建模与仿真,并输出vr虚拟现实…

政安晨:【深度学习实践】【使用 TensorFlow 和 Keras 为结构化数据构建和训练神经网络】(四)—— 过拟合和欠拟合

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 通过增加容量或提前停止来提高性能。 在深度学习中&#…

Springboot 整合 Knife4j (API文档生成工具)

目录 一、Knife4j 介绍 二、Springboot 整合 Knife4j 1、pom.xml中引入依赖包 2、在application.yml 中添加 Knife4j 相关配置 3、打开 Knife4j UI界面 三、关于Knife4j框架中常用的注解 1、Api 2、ApiOperation ​3、ApiOperationSupport(order X) ​4、ApiImplici…

C# WPF编程-布局

C# WPF编程-布局 布局WPF布局原则布局过程布局容器布局属性Border控件StackPanel布局WrapPanel布局DockPanel布局Grid布局UniformGrid布局Canvas布局 布局 WPF布局原则 WPF窗口只能包含单个元素。为在WPF窗口中放置多个元素并创建更贴近实用的用户界面&#xff0c;需要在窗口…

linux系统----------MySQL索引浅探索

目录 一、数据库索引介绍 二、索引的作用 索引的副作用 (缺点) 三、创建索引的原则依据 四、索引的分类和创建 4.1普通索引 4.1.1直接创建索引 4.1.2修改表方式创建 4.1.3创建表的时候指定索引 4.2唯一索引 4.2.1直接创建唯一索引 4.2.2修改表方式创建 4.2.3创建表…

根据log信息解读内核(linux-2.6.32.24)的启动流程

目录 概述 1 从bootloader 到内核部分 2 初始化cache和CPU时钟 3 获取cache和memory信息 4 初始化cache、电源管理和中断 5 初始化USB和I2C 6 网络协议初始化 7 挂载JFFS2文件系统和初始化IO 8 初始化外围device 9 Nand Flash资源分配 10 初始化网络接口 11 注册US…

一文快速掌握docker的理念和基本使用

写在文章开头 写于一个周末&#xff0c;在复盘梳理文章时候发现这一篇关于早期了解docker时记录的文档&#xff0c;仔细阅读了一下&#xff0c;为了保证文章更加清晰以便读者使用。故再次重新一次梳理一次&#xff0c;通过这篇文章&#xff0c;你将会对docker的基本理念和基础…

Expert Prompting-引导LLM成为杰出专家

ExpertPrompting: Instructing Large Language Models to be Distinguished Experts 如果适当设计提示&#xff0c;对齐的大型语言模型&#xff08;LLM&#xff09;的回答质量可以显著提高。在本文中&#xff0c;我们提出了ExpertPrompting&#xff0c;以激发LLM作为杰出专家回…

【C语言基础】:字符串函数(二)

文章目录 一、strncpy函数的使用二、strncat函数的使用三、strncmp函数的使用四、strstr函数的使用和模拟实现4.1 strstr函数的使用4.2 strstr函数的模拟实现 五、strtok函数的使用六、strerror函数的使用 上节回顾&#xff1a;【C语言基础】&#xff1a;字符函数和字符串函数 …

【ubuntu20.04+tensorflow-gpu1.14配置】

ubuntu20.04tensorflow-gpu1.14配置 目录0. 版本注意事项说明1. 个人目录下载后配置系统环境变量2. anaconda配置所有环境&#xff08;过程简便&#xff0c;但容易出现不兼容问题&#xff09;3. 验证tensorflow-gpu4. 一些细节 目录 总结出两种方法 个人目录 下载cuda和cudnn…

分库分表场景下多维查询解决方案(用户+商户)

在采用分库分表设计时&#xff0c;通过一个PartitionKey根据散列策略将数据分散到不同的库表中&#xff0c;从而有效降低海量数据下C端访问数据库的压力。这种方式可以缓解单一数据库的压力&#xff0c;提升了吞吐量&#xff0c;但同时也带来了新的问题。对于B端商户而言&#…