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

news2024/11/14 15:09:14

文章目录

    • 第三章 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 语句并交由数据库执行。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive和Hadoop版本对应关系

通过 Downloads (apache.org) 即可查看

MySQL的基本操作

目录 引言 一、SQL语句简介 &#xff08;一&#xff09;SQL通用语法 &#xff08;二&#xff09;SQL分类 &#xff08;三&#xff09;数据类型 1.数值类型 2.字符串类型 3.日期/时间类型 4.修饰符 二、登录mysql服务 三、SQL语句操作 &#xff08;一&#xff09;DD…

vue3 + ts +element-plus + vue-router + scss + axios搭建项目

本地环境&#xff1a; node版本&#xff1a;20.10.0 目录 一、搭建环境 二、创建项目 三、修改页面 四、封装路由vue-router 五、element-plus 六、安装scss 七、封装axios 一、搭建环境 1、安装vue脚手架 npm i -g vue/cli 2、查看脚手架版本 vue -V3、切换路径到需…

Studio One 6 Mac中文版破解版下载(附Mac版注册机)

Studio One 6 Mac版是一款强大的音乐创作与制作软件&#xff0c;其可通过更简单的方式来录制音频及进行MIDI制作&#xff0c;并提供丰富的专业功能。它具备音乐创作、录音混缩、MIDI编辑、音频处理、Loops拼接、视频配乐和母带与专辑制作等功能。软件提供了强大的音频性能&…

在iOS中安装

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;使用CUDA 为Tegra构建OpenCV-CSDN博客 下一篇&#xff1a; 警告&#xff01; 本教程可以包含过时的信息。 所需软件包 CMake 2.8.8 或更高版本Xcode 4.2 或更高版本 从 G…

笔试总结01

1、spring原理 1、spring原理 spring的最大作用ioc/di,将类与类的依赖关系写在配置文件中&#xff0c;程序在运行时根据配置文件动态加载依赖的类&#xff0c;降低的类与类之间的藕合度。它的原理是在applicationContext.xml加入bean标记,在bean标记中通过class属性说明具体类…

旅游小程序的市场与发展趋势

随着科技的发展&#xff0c;移动互联网已经成为我们生活中不可或缺的一部分。在这个时代&#xff0c;小程序已经成为了一种新的趋势&#xff0c;尤其是在旅游行业。那么&#xff0c;旅游小程序有哪些市场&#xff0c;发展趋势又怎么样呢&#xff1f; 一、旅游小程序的市场 1. 用…

AI原生安全 亚信安全首个“人工智能安全实用手册”开放阅览

不断涌现的AI技术新应用和大模型技术革新&#xff0c;让我们感叹从没有像今天这样&#xff0c;离人工智能的未来如此之近。 追逐AI原生&#xff1f;企业组织基于并利用大模型技术探索和开发AI应用的无限可能&#xff0c;迎接生产与业务模式的全面的革新。 我们更应关心AI安全原…

Linux的基本使用

1.Linux的背景 1.1什么Linux Linux是⼀个操作系统.和Windows是"并列"的关系. 1.2Linux系统的优势 1. 开源(意味着免费,便宜) 2. 稳定(Linux可以运⾏很多年,都不会发⽣重⼤问题) 3. 安全(Linux只有管理员或者特定⽤⼾才能访问Linux内核) 4. ⾃由(不会被强加商业产品和…

JVM内存划分

一、运行时数据区域 堆、方法区&#xff08;元空间&#xff09;、虚拟机栈、本地方法栈、程序计数器。 Heap(堆)&#xff1a; 对象的实例以及数组的内存都是要在堆上进行分配的&#xff0c;堆是线程共享的一块区域&#xff0c;用来存放对象实例&#xff0c;也是垃圾回收&…

用大语言模型控制交通信号灯,有效缓解拥堵!

城市交通拥堵是一个全球性的问题&#xff0c;在众多缓解交通拥堵的策略中&#xff0c;提高路口交通信号控制的效率至关重要。传统的基于规则的交通信号控制&#xff08;TSC&#xff09;方法&#xff0c;由于其静态的、基于规则的算法&#xff0c;无法完全适应城市交通不断变化的…

RK3568笔记二十:PP-YOLOE部署测试

若该文为原创文章&#xff0c;转载请注明原文出处。 注&#xff1a;转换测试使用的是Autodl服务器&#xff0c;CUDA11.1版本&#xff0c;py3.8。 一、PP-YOLOE环境安装 创建环境 # 使用 conda 创建一个名为 PaddleYOLO 的环境&#xff0c;并指定 python 版本conda create -n…

[flask]flask的路由

路由的基本定义 路由就是一种映射关系。是绑定应用程序&#xff08;视图&#xff09;和url地址的一种一对一的映射关系&#xff01;在开发过程中&#xff0c;编写项目时所使用的路由往往是指代了框架/项目中用于完成路由功能的类&#xff0c;这个类一般就是路由类&#xff0c;…

电子方案定制 /家庭K歌话筒

无线K歌话筒是一种可以与智能手机、平板电脑或其他设备无线连接的话筒&#xff0c;主要用于唱歌、录音和娱乐。 东莞市酷得智能科技有限公司&#xff0c;作为一家专业的玩具底层方案服务商&#xff0c;与国内外多家优秀制造企业有着深度合作&#xff0c;始终坚持以客户为中心&…

从相机空间到像素空间的投影和反投影原理和代码

目录 从相机空间到像素空间的投影 效果 ​编辑 公式 ​编辑 代码 像素空间到相机空间的反投影 记录一下从相机空间到像素空间的投影&#xff08;3D-->2D&#xff09;和像素空间到相机空间的反投影&#xff08;2D-->3D&#xff09;。 推荐blog&#xff1a;SLAM入门之视…

【.net/.net core】后台生成echarts图片解决方案及.net core html转word方法

工具环境下载&#xff1a; EChartsConvert&#xff1a;https://gitee.com/saintlee/echartsconvert EChartsConvert为生成echarts图片的服务端&#xff0c;用于接收参数和生成echarts图表图片BASE64编码 PhantomJS:Download PhantomJS PhantomJS用来发布EChartsConvert服务…

Amazon SageMaker + Stable Diffusion 搭建文本生成图像模型

如果我们的计算机视觉系统要真正理解视觉世界&#xff0c;它们不仅必须能够识别图像&#xff0c;而且必须能够生成图像。文本到图像的 AI 模型仅根据简单的文字输入就可以生成图像。 近两年&#xff0c;以ChatGPT为代表的AIGC技术崭露头角&#xff0c;逐渐从学术研究的象牙塔迈…

基于python+vue云上水果超市的设计与实现flask-django-php-nodejs

本论文的主要内容包括&#xff1a; 第一&#xff0c;研究分析当下主流的web技术&#xff0c;结合超市日常管理方式&#xff0c;进行云上水果超市的数据库设计&#xff0c;设计云上水果超市功能&#xff0c;并对每个模块进行说明。 第二&#xff0c;陈列说明该系统实现所采用的架…

kubernetes-RBAC 鉴权

kubernetes-RBAC 鉴权 kubernetes-RBAC 鉴权1、查看整个k8s集群内部有哪些资源2、什么是鉴权2.1、鉴权的目的2.2、怎么去鉴权&#xff1a;RBAC2.3、对鉴权的理解&#x1f331;2.4、租户 3、对一些概念的解释3.1、概念简述3.2、账号类型&#xff1a;Useraccount和ServiceAccount…

AI绘画可以稳定生成中文了:白嫖阿里云部署AnyText

长久以来&#xff0c;在AI绘画中书写文字一直是个难题。即使到了SDXL时代&#xff0c;我们也只能输出英文&#xff0c;而且还经常出现漏掉字母的情况。现在阿里达摩院搞出了一个解决方案&#xff0c;可以在Stable Diffusion生成的作品中稳定输出中、英、日、韩等多种文字&#…