《深入解析 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 对象初始化器

对象初始化器的作用只是表达应该如何初始化每个属性。
注意,只有在使用对象初始化器或者集合初始化器时,构造器的参数列表才再以省略。
- 如果初始化值(“=” 右边的内容)是一个普通的表达式,那么会先计算该表达式的值,然后将结果传给属性对应的 set 访问器。
- 如果初始化值是另一个对象初始化器,则不会调用 set 访问器,而会调用 get 访问器,然后将嵌套对象初始化器得到的结果应用于由 get 访问器返回的属性。

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

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

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

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

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


只有实现了 IEnumerable 接口的类型才能够使用集合初始化器。
3.4 匿名类型
3.4.1 基本语法和行为
使用匿名类型可以更精练地表达“一次性”的类型需求,同时还不失静态类型的优势:

-
匿名类型的语法类似于对象初始化器,但无须指定类型名称,只需要 new 关键字、左大括号、属性以及右大括号。这一形式称为匿名对象创建表达式。
-
声明 player 变量使用了 var 关键字,因为所创建的类型是匿名类型,所以只能用 var 来声明(也可以使用 object 来声明,不过意义不大)。
-
以上代码依然属于静态类型的范畴。Visual Studio 会为 player 变量自动设置 Name 和 Score 属性。如果要访问一个不存在的属性(比如 player.Points),则编译器会报错。
-
属性的类型是根据赋值的类型进行推断的:player.Name 是 string 类型,player. Score 是 int 类型。
投射初始化器:
可以从其他对象复制属性或字段到新对象中,并且二者的属性或字段名称相同。

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

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

可以直接简化为:

说明:
尽管以上两种形式的代码结果相同,但不是所有行为都相同。
例如,在项目中将 Address 属性重命名为 CustomerAddress,若使用投射初始化器,那么 flattenedItem.Address 也将变为 flattenedItem.CustomerAddress。
3.4.2 编译器生成类型
虽然源码中没有出现匿名类型的名称,但编译器需要为它生成一个类型。
- 它在执行期没有任何特殊之处,对于执行期来说也只是一个普通的类型而已。
- 该类型的名称不是一个有效的 C# 名称。
关于该类型,还有几个比较有意思的特征(其中一些得到了语言规范层面的保证):
- 它是一个类(保证)。
- 其基类是 object (保证)。
- 该类是密封的(不保证,虽然非密封的类并没有什么优势)。
- 属性是只读的(保证)。
- 构造器的参数名称与属性名称保持一致(不保证,有时对于反射有用)。
- 对于程序集是 internal 的(不保证,在处理动态类型时会比较棘手)。
- 该类会覆盖 GetHashCode() 和Equals() 方法:两个匿名类型只有在所有属性都等价的情况下才等价(可以正常处理 null 值)。只保证会覆盖这两个方法,但不保证散列值的计算方式。
- 覆盖并完善 ToString() 方法,用于呈现各属性名称及其对应值。这一点不保证,但对于问题诊断来说作用重大。
- 该类型为泛型类,其类型形参会应用于每一个属性。具有相同属性名称但属性类型不同的匿名类型,会使用相同的泛型类型,但拥有不同的类型实参。这一点不保证,不同编译器的实现方式不同。
- 如果两个匿名对象创建表达式使用相同的属性名称,具有相同的属性类型以及属性顺序, 并且在同一个程序集中,那么这两个对象的类型相同。
可以利用第 10 点使用匿名类型来创建隐式类型数组:

3.4.3 匿名类型的局限性
- 难以应用于方法签名中。即, 难以在多处使用同一个匿名类型。
- 匿名类型不提供任何数据封装。即,匿名类型中不能有校验,也不能添加任何行为。
3.5 lambda 表达式
3.5.1 捕获变量
给出如下设计好的代码示例:

- instanceField 是 CapturedVariablesDemo 类的一个实例字段,被 lambda 表达式所捕获。
- methodParameter 是 CreateAction 方法的一个参数,被 lambda 表达式所捕获。
- methodLocal 是 CreateAction 方法中的一个局部变量,被 lambda 表达式所捕获。
- uncaptured 是 CreateAction 方法中的一个局部变量,因为没有被 lambda 表达式使用,所以不属于捕获变量。
- lambdaParameter 是 lambda 表达式自己的参数,不属于捕获变量。
- lambdaLocal 是 lambda 表达式内部的局部变量,不属于捕获变量。
通过生成类来实现捕获变量
- 没有捕获任何变量,编译器会创建一个静态方法,不需要额外的上下文。
- 仅捕获了实例字段,编译器会创建一个实例方法。实例字段的捕获数目没有影响,只需要一个 this 便都可以访问。
- 捕获了局部变量或者参数,编译器会创建一个私有的嵌套类用于保存上下文信息,在该类中创建一个实例方法用于容纳原 lambda 表达式内容,并使用嵌套类来访问捕获变量。
应用上述规则,编译器转义后的代码如下:

说明:
具体实现细节因编译器而异。例如对于没有捕获变量的 lambda 表达式,编译器可能会创建一个包含一个实例方法的嵌套类,而不是创建一个静态方法。委托的执行效率会因创建方式的不同而略有差异。这里只描述编译器为访问捕获变量所做的那些必要、基本的工作, 其复杂度可能根据实际需要而增加。
局部变量的多次实例化
简单起见,下列代码不捕获参数和实例字段,只捕获一个局部变量:

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

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

其执行结果如下:

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

大多数情况很少需要查看这样的代码,但编译器生成代码的方式会对程序性能有不小的影响。如果在性能敏感的代码中使用 lambda 表达式,那么需要注意可能会因为变量捕获而创建过多对象,从而影响性能。
3.5.2 表达式树
lambda 表达式可以由编译器转换成表达式树。表达式树是将代码按照数据来表示的一种形式。这项特性是 LINQ 能够有效处理 SQL 数据库的核心秘诀所在。通过表达式树,C# 的代码可以在执行期被分析并转换成 SQL。 委托的作用是提供可运行的代码,而表达式树的作用是提供可查看的代码(这有点类似于反射机制)。虽然也可以在代码中直接构建表达式树,但更普遍的做法是让编译器负责把 lambda 表达式转换成表达式树。
以下面的 lambda 表达式为例:

编译器并未在任何地方生成一个硬编码的字符串。以上字符串是通过表达式树动态构建出来的。这段代码表明:代码是可以进行执行期检查的。这就是表达式树的所有关键所在。
首先看 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 表达式来为该变量赋值。编译器负责生成适用于执行期的表达式树。示例代码如下:


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

- 从 .NET 3.5 开始,表达式树 API 就已经扩展支持代码块和其他构建了,但 C# 编译器依然保留了该限制,而且对于 LINQ 使用的表达式树也有同样的限制。
- 这是对象初始化器和集合初始化器很重要的原因:可以在一个表达式内完成初始化,以供表达式树使用。
- 另外,lambda 表达式不能使用赋值运算符,也不能使用 C# 4 的动态类型和 C# 5 的异步。
将表达式树编译成委托
表达式树可用于在执行期动态构建委托。这种方式一般需要手动编写部分代码,而不是使用 lambda 表达式进行转化。
Expression<TDelegate> 有一个 Compile() 方法,该方法返回一个委托类型。该委托类型与普通的委托类型无异。
以上述代码为例,构建出 adder 表达式树,将其编译成一个委托,然后调用该委托并打印出结果:

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

编译器唯一需要做的就是为扩展方法及其所在类添加[Extension]特性。该特性在命名空间 System.Runtime.CompilerServices 下。其本质上是一个标记,标记 ToInstant() 方法可以按照 DateTimeOffset 的实例方法那样凋用。
3.6.2 调用扩展方法
扩展方法可以在其第一个参数的类型实例上以实例方法的调用方式进行调用,但还需要一个前提:让编译器可以查找到这个扩展方法。
优先级问题
-
如果存在一个与该类同名的普通实例方法,那么编译器总是会优先选择该实例方法来调用。
- 在此过程中,无所谓扩展方法是否具有更匹配的形参。如果编译器查找到有可调用的实例方法,就不会再去查找扩展方法了。
-
如果编译器没有找到可调用的实例方法,那么会开始查找扩展方法。首先查找扩展方法调用代码所在的命名空间以及所有 using 指令指定的命名空间。

编译器会从以下位置查找扩展方法:
- CSharpInDepth.Chapter03 命名空间下的静态类;
- CSharpInDepth 命名空间下的静态类;
- 全局命名空间下的静态类;
- using 指令指定的命名空间下的静态类(例如 using System 这样的指向命名空间的命令);
- (只在 C#6 中)using static 指定的静态类,10.1节还会介绍。
补充:
- 编译器会从最内层的命名空间一路向外查找至全局命名空间。在查找的每条路径上,都要查找当前命名空间下的静态类,或者查找 using 指令指定的命名空间中的类。
- 查找的顺序并不重要。如果调整 using 指令的顺序后影响了扩展方法的查找结果,建议将扩展方法重新命名。
- 查找的每一步中都有可能找到多个适合调用的扩展方法。此时编译器会对当前所有候选方法执行常规的重载决议。
- 在决策完成与,编译器为调用扩展方法所生成的 IL 代码和调用普通静态方法所生成的 IL 代码是完全相同的。
说明:
x.Method(y);
如果 Method 是实例方法,x 为 null,就会抛出 NulLReferenceException;
而如果 Method 是一个扩展方法,那么即便 x 为 null,也会将 x 作为其首个参数进行方法调用。
3.6.3 扩展方法的链式调用
下面示例代码是一个简单查询:现有一个单词序列,按照单词长度进行筛选,并将其按字母顺序排序,然后全部转换为大写。该查询只用到了 C#3 中的 lambda 表达式和扩展方法这两个特性。

注意:以上代码中 Where、OrderBy 和 Select 三个调用的顺序就是操作实际发生的顺序。由于 LINQ 中存在延退和优化策略,很难知道具体何时会执行什么操作,但代码的阅读顺序和执行顺序是一致的。
下列代码实现了上述相同的查询功能,但没有使用扩展方法。

对比之下可以发现明显的缺陷:代码阅读起来很困难。代码中方法调用的顺序和实际执行的顺序刚好相反:Where 方法是第一个被调用的,却放在了末尾。lambda 表达式 word => word.ToUpper() 究竟属于哪个方法调用很不明确。它本属于 Select 方法, 但和 Select 中间隔了一堆代码。
还有一个解决方法是将每个方法调用的结果都赋给一个局部变量,然后通过上一个变量再继续调用下一个方法。但大量额外的局部变量容易造成混淆且会分散注意力。

由上可见,方法的链式调用带来的好处不仅仅限于 LINQ。一个方法调用的结果用作另一个方法调用的开始。扩展方法能让我们以可读性强的方式编码任何类型,而且不局限于那些已经支持链式调用的类型。
3.7 查询表达式
虽然几乎 C# 3 的所有特性都对 LINQ 有所贡献,但只有查询表达式是专门为 LINQ 设计的。 使用查询表达式,我们可以通过查询专用语句(select, where、let、group by 等)编写简洁的查询代码。由编译器负责把查询表达式翻译成非查询语句的形式,并进行常规编译。回顾一下 3.6.3 节的代码:

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

3.7.1 从 C# 到 C# 的查询表达式转换
语言规范直接将查询表达式定义为一种语法转译,且该转译过程发生在绑定或重载决议之前。即,查询表达式会首先被编译器转义为可执行的 C# 代码。很多时候,转译的结果就是使其变成对应的扩展方法调用,不过语言规范并没有强制要求该行为。
3.7.2 范围变量和隐形标识符
查询表达式引入了范围变量的概念。范围变量与普通变量不同,范围变量充当了查询语句中每条子句中的输入。
在上一个例子中,位于查询表达式起始位置的 from 子句引入了范围变量(加粗部分):

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

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

这里的 tmp 不属于查询转译的一部分,语言规范中是用 * 符号表示的。在语言规范并没有规定为查询构建表达式树时,参数应当使用什么名称。这个名称本身不重要,因为在编写查询时它是不可见的,因此把它称为隐形标识符。
3.7.3 选择使用哪种 LINQ 语法
- 查询表达式:更适合大规模查询,表现出众,可读性强。
- 必须以 from 子句开始,以 select 或者 group by 子句结尾。
- 方法语法:更适合简单查询,简单明了。
例如:

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

说明:
对于采用非查询表达式的语法,目前没有统一的术语,而有方法语法、点式语法、流式语法、lambda 语法等名称,之后会统一采用方法语法来代称。
当查询变得更复杂时,方法语法依然可以从容应对:
-
LINQ 中提供的很多方法,并没有与之对应的查询表达式语法。
- 例如 Select 和 Where 的某些重载方法,返回的是元素以及元素对应的索引值。
-
如果想在查词的结尾执行一个方法调用(例如调用 ToList() 来把结果转换成 List<T> 对象),就要把整个查询表达式用圆括号括起来;
如果使用方法语法,只需在末尾直接添加方法调用即可。
在很多情况下(包括上述例子在内),两种方式难分高下。
3.8 终极形态:LINQ
下面介绍 C#3 特性是如何成就 LINQ 的。假设有一个查询从 Entity Framework 获取数据,代码如下所示(假设已存在某数据库和相应的表结构):

短短 4 行代码,应用了所有新特性。
-
匿名类型.
包括投射初始化器(只选择 name 和 price 这两个属性)。
-
使用 var 声明的匿名类型.
因为无法声明 products 变量的有效类型。
-
查询表达式。
当然对于本例可以不使用查询表达式,但对于更复杂的情况,使用查询表达式能事半功倍。
-
lambda 表达式。
lambda 表达式在这里作为查询表达式转译之后的结果。
-
扩展方法。
使得转译后的查询可以通过 Queryable 类实现,因为 dbContext.Products 实现了 IQueryable<Product>接口。
-
表达式树。
使得查询逻辑可以按照数据的方式传给 LINQ 提供器,然后转换成 SQL 语句并交由数据库执行。
相关文章:

《深入解析 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 匿名类型的局限…...
Redis 的5种数据类型的基本命令
目录 String的基本命令 1. SET 2. GET 3. GETSET 4. STRLEN 5. APPEND 6. SETRANGE 7. GETRANGE 8. SETEX 9. SETNX 10. MSET 11. MGET 12. INCR 13. DECR 14. INCRBY 15. DECRBY 16. INCRBYFLOAT Map的基本命令 1. HSET 2. HGET 3. HMSET 4. HMGET 5. …...

【Liunx-后端开发软件安装】Liunx安装nginx
【Liunx-后端开发软件安装】Liunx安装nginx 使用安装包安装 一、简介 nginx,这个家伙可不是你厨房里的那位大厨,它可是互联网世界的“煎饼果子摊主”。想象一下,在熙熙攘攘的网络大街上,nginx挥舞着它的锅铲——哦不,是…...

力扣Lc20--- 202.快乐数(java版)-2024年3月20日
1.题目 2.知识点 (1)while (seen.contains(n) false) { // 循环体 } 与 !seen.contains(n) 等同 (2) 当传入数字 19 给 isHappy(19) 方法时,下面是每一行代码的执行过程: 初始化一个空的 HashSet&#…...

机器学习----交叉熵(Cross Entropy)如何做损失函数
目录 一.概念引入 1.损失函数 2.均值平方差损失函数 3.交叉熵损失函数 3.1信息量 3.2信息熵 3.3相对熵 二.交叉熵损失函数的原理及推导过程 表达式 二分类 联立 取对数 补充 三.交叉熵函数的代码实现 一.概念引入 1.损失函数 损失函数是指一种将一个事件&#x…...

Linux docker3--数据卷-nginx配置示例
一、因为docker部署服务都是以最小的代价部署,所以通常在容器内部很多依赖和命令无法执行。进入容器修改配置的操作也比较麻烦。本例介绍的数据卷作用就是将容器内的配置和宿主机文件打通,之后修改宿主机的配置文件就相当于修改了docker进程的配置文件&a…...

力扣454. 四数相加 II
思路:把四个数组拆成两对,两个分别相加,记录第一对的相加结果进map里,再把第二对数组 0-nums2-nums4 去map里面找出现了几次,这题不用对重复的四元组去重,所以出现多次都有效。 class Solution {public int…...

vulnstack1 渗透分析 红日靶场(一)
环境搭建 ip段设置 kali (coleak):192.168.145.139 Windows 7 (stu1):192.168.10.181、192.168.145.140 Winserver 2008 (owa):192.168.10.180 Win2k3 (root-tvi862ubeh):192.168.10.182复制 kali可以访问win7,但不能…...

外包干了6天,技术明显进步。。。
我是一名大专生,自19年通过校招进入湖南某软件公司以来,便扎根于功能测试岗位,一晃便是近四年的光阴。今年8月,我如梦初醒,意识到长时间待在舒适的环境中,已让我变得不思进取,技术停滞不前。更令…...
比较好的知识点
2023年Java超全面试题及答案解析---https://blog.csdn.net/qq_42301302/article/details/128785274 7分钟带你细致解析4个Java算法必刷题---https://blog.csdn.net/hcxy2022/article/details/127963797 50道JAVA基础算法编程题【内含分析、程序答案】---https://blog.csdn.net/…...

抖音开放平台的订单类API接口调用测试指南(内含详细步骤)
一、什么是抖音开放平台 抖音开放平台基于抖音母体,提供抖音服务基础设施和创新行业解决方案的平台。同时满足各类各类机构、创作者及服务商对于内容获取、分享的个性化需求,我们诚邀各个行业、不同阶段的合作伙伴与我们一起,共建内容良性生…...
HiveSQL一本通 - 案例实操
文章目录 0.HiveSQL一本通使用说明6.综合案例练习之基础查询6.1 环境准备创建数据表数据准备加载数据 6.2 简单查询练习1.查询姓名中带“山”的学生名单2.查询姓“王”老师的个数3.检索课程编号为“04”且分数小于60的学生的分数信息,结果按分数降序排列4.查询数学成…...

Axure RP 8中文---快速原型设计工具,一站式解决方案
Axure RP 8是一款专业的快速原型设计工具,以其直观易用的界面和丰富的功能受到广大用户的青睐。它支持用户通过拖放操作快速创建交互式原型,包括线框图、流程图等,并具备高保真度的设计能力。Axure RP 8还提供了团队协作和共享功能࿰…...
Available platform plugins are: minimal, offscreen, webgl, windows.
我在运行pyqt5开发的代码时,报错: This application failed to start because no Qt platform plugin could be initialized, Reinstalling the application may fix this problem. Available platform plugins are: minimal, offscreen, webgl, windows…...

创意无限,风险有度:2024愚人节海外网红营销策略解析
2024年愚人节即将到来,这个充满趣味与惊喜的节日,既是人们展示幽默与创意的舞台,也是品牌进行营销活动的绝佳时机。在这个特殊的日子里,通过海外网红营销来推广品牌或产品,无疑是一种富有创意的营销策略,但…...
深入理解 Session、Cookie 和 Token:网络安全和身份验证的重要概念
深入理解 Session、Cookie 和 Token:网络安全和身份验证的重要概念 在当今数字化的世界中,网络安全和身份验证是至关重要的议题。为了实现这些目标,我们常常使用诸如 Session、Cookie 和 Token 等概念。这些概念在 Web 开发、网络通信和安全…...
镜像站汇总
软件镜像站 查看linux版本,常见有centos, ubuntu, Debian cat /etc/os-release去清华软件源帮助页面,查找对应源设置方案(需要结合具体的系统版本),常用: Debian https://mirrors.tuna.tsinghua.edu.cn/help/debian/ 需要选则系…...

设计模式之抽象工厂模式解析
抽象工厂模式 1)问题 工厂方法模式中的每个工厂只生产一类产品,会导致系统中存在大量的工厂类,增加系统的开销。 2)概述 a)产品族 和 产品等级结构 产品等级结构:产品的继承结构; 产品族&…...

【毕设级项目】基于ESP8266的家庭灯光与火情智能监测系统——文末源码及PPT
目录 系统介绍 硬件配置 硬件连接图 系统分析与总体设计 系统硬件设计 ESP8266 WIFI开发板 人体红外传感器模块 光敏电阻传感器模块 火焰传感器模块 可燃气体传感器模块 温湿度传感器模块 OLED显示屏模块 系统软件设计 温湿度检测模块 报警模块 OLED显示模块 …...
UnityShader(十九) AlphaBlend
上代码: Shader "Shader入门/透明度效果/AlphaBlendShader" {Properties{_MainTex ("Texture", 2D) "white" {}_AlphaScale("AlphaScale",Range(0,1))1.0}SubShader{Tags { "RenderType""Transparent&quo…...

SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...

【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...