C#源代码生成器深入讲解一

C#源代码生成器

01 源代码生成器初体验

  1. 新建一个类库,一定是standard2.0版本,否则会出问题。
  2. 引用Nuget包Microsoft.CodeAnalysis.Common
  3. 新建一个类,继承自ISourceGenerator接口
//一定要写,制定语言
[Generator(LanguageNames.CSharp)]
public sealed class GreetingGenerator : ISourceGenerator
{//源代码生成器的所要生成的方法public void Execute(GeneratorExecutionContext context){//建议名称使用.g.cs//建议使用全局命名空间global::  为了防止诸如System和Windows.System冲突context.AddSource("Greeting.g.cs",$$"""//加上这句话,告知编译器,这个文件是由源代码生成器生成的,//防止编译器进行代码分析,避免不必要的编译器警告//<auto-generated>namespace GreetingTest;//配置预处理指令#nullable enable//告知源代码生成器生成的代码[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] //告知由哪个源代码生成器生成的代码[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(GreetingGenerator)}}","1.0")] public static class Greeting{//告知源代码生成器生成的代码[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute] //告知由哪个源代码生成器生成的代码[global::System.CodeDom.Compiler.GeneratedCodeAttribute("{{nameof(GreetingGenerator)}}","1.0")] public static void SayHello(string name){global::System.Console.WriteLine($"Hello, World {name}!");}}""");}//源代码生成器本身的初始化方法public void Initialize(GeneratorInitializationContext context){}
}

注意事项

  • 在使用某些方法或者特性时,最好写全命名空间,并用global::命名空间限定符
  • 文件名称建议使用.g.cs后缀
  • 建议在开头增加<auto-generated>注释
  • 可以使用原生字符串符号"""三个双引号,以及双内插符号$$,这样可以使用{{}}来进行内插
  1. 建立一个控制台项目,并引用刚才的类库,要加上OutputItemType和 ReferenceOutAssembly

    <ItemGroup><!--ReferenceOutAssembly设定false,表示不会将生成器的作为引用,而是将分析器生成的代码。--><ProjectReference Include="..\SourceGeneratorConsole.Generator\SourceGeneratorConsole.Generator.csproj" OutputItemType="Analyzer" ReferenceOutAssembly="false" />
    </ItemGroup>
    
  2. 使用源代码生成器,使用生成器中所生成的方法。

using GreetingTest;
Greeting.SayHello("李四");

02 使用分部类型

很多时候,不需要源代码生成器生成完整的类型,而是和主程序交互,分别形成一定的代码,此时可以使用分部类型来实现。

  1. 在上一章节中的控制台项目中增加一个类
namespace GreetingTest
{public static partial class GreetingUsePartialClass{public static partial void SayHello(string name);}
}
  1. 修改上一章节中源代码生成器类库项目
namespace SourceGeneratorConsole.Generator;[Generator(LanguageNames.CSharp)]
public sealed class GreetingGenerator : ISourceGenerator
{//源代码生成器的所要生成的方法public void Execute(GeneratorExecutionContext context){//修改为GreetingUsePartialClass.g.cs,和控制台中定义的名称相对应context.AddSource("GreetingUsePartialClass.g.cs",$$"""//<auto-generated>namespace GreetingTest;//分部类可以省略public static等,只要在一个地方定义了就可以了partial class GreetingUsePartialClass{//分部方法必须写全public static partial void SayHello(string name){global::System.Console.WriteLine($"Hello, World {name}!");}}""");}//源代码生成器本身的初始化方法public void Initialize(GeneratorInitializationContext context){}
}
  1. 在控制台应用中调用
static void Main(string[] args)
{GreetingUsePartialClass.SayHello("Source Generator");Console.Read();
}

03 使用SyntaxReceiver属性

上一章节中,在源代码生成器中将类名和方法名写进去了,源代码生成器往往是应用在不同的项目中,类型名和方法名都不是固定的,所以要动态的修改名称,这就要用到了SyntaxContextReceiver属性。

  1. 在上一章节中的源代码生成器文件中,写一个SyntaxReceiver
//file只在本文件可以用,跟internal一样是访问修饰符
//提供一个语法搜索类型,这个类型只用于寻找主要项目里的指定语法满足条件部分
file sealed class SyntaxReceiver:ISyntaxReceiver
{//表示一个方法的语法节点,这个方法就是用到的SayHello方法,这个方法的返回值是void,静态、partialpublic MethodDeclarationSyntax? SayHelloToMethodSyntaxNode {private set; get; }public void OnVisitSyntaxNode(SyntaxNode syntaxNode){//检查syntaxNode是否是类型定义,且Modifiers属性不为空if (syntaxNode is not TypeDeclarationSyntax { Modifiers:var modifiers and not [] }){return;}//如果类型不包含partial关键字if (!modifiers.Any(SyntaxKind.PartialKeyword)){return;}//判断子节点,也就是类型内部的成员是否有partialforeach (var childrenNode in syntaxNode.ChildNodes()){// 判断当前语法节点是否是一个合理的方法定义。// 该方法名为 SayHelloTo// 该方法返回一个 void 类型。// 该方法还需要额外的修饰符(一会儿要用来判断 partial 关键字)。if (childrenNode is not MethodDeclarationSyntax { Identifier:{ ValueText: "SayHello" },ReturnType:PredefinedTypeSyntax{Keyword.RawKind:(int)SyntaxKind.VoidKeyword},Modifiers:var childrenModifiers and not []} possibleMethodDeclarationSyntax){continue;}// 该方法必须有 partial 关键字的存在。if (!childrenModifiers.Any(SyntaxKind.PartialKeyword)){continue;}if (SayHelloToMethodSyntaxNode is null){SayHelloToMethodSyntaxNode = possibleMethodDeclarationSyntax;return;}}}
}
  1. 修改属性生成器
[Generator(LanguageNames.CSharp)]
public sealed class GreetingGenerator : ISourceGenerator
{//源代码生成器的所要生成的方法public void Execute(GeneratorExecutionContext context){var syntaxReceiver = (SyntaxReceiver)context.SyntaxReceiver;//{}为属性模式匹配,在此处表示不为空,not {}表示为空if (syntaxReceiver.SayHelloToMethodSyntaxNode is not {} methodSyntax){return;}var type = methodSyntax.Ancestors().OfType<TypeDeclarationSyntax>().First();var typeName = type.Identifier.ValueText;//建议名称使用.g.cs//建议使用全局命名空间global::  为了防止诸如System和Windows.System冲突context.AddSource($"{typeName}.g.cs",$$"""//加上这句话,告知编译器,这个文件是由源代码生成器生成的,//防止编译器进行代码分析,避免不必要的编译器警告//<auto-generated>namespace GreetingTest;partial class {{typeName}}{public static partial void SayHello(string name){global::System.Console.WriteLine($"Hello, World {name}!");}}""");}//源代码生成器本身的初始化方法public void Initialize(GeneratorInitializationContext context){//注册一个语法的通知类型,作用是运行源代码生成器的时候,去检查固定语法是否满足条件context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());}
}

在Initialize中返回刚才创建的类, Execute方法中获得相应的类名称。

  1. 调用
static void Main(string[] args)
{GreetingUsePartialClass.SayHello("Source Generator");Console.Read();
}

image-20231110102144123

04 调试源代码生成器

源代码生成器是在编译阶段中自动生成,一般无法调试,这时可以在源代码生成器中的Initialize方法中加上

//添加调试器,如果程序没有调试器的时候就启动
//如果用了多个源代码生成器,只要有一个配置了这个,也可以调试其他的
//if (!Debugger.IsAttached)
//{
//    Debugger.Launch();
//}

05 ISyntaxContextReceiver属性

上面是已知有了SayHello的方法,假设不知道是什么方法名,如何使用源代码生成器,本节借助特性来实现

  1. 在主项目中声明特性,一般都是放在主项目中,因为在主项目中的引用其他项目的设置中已设置了OutputItemType="Analyzer" ReferenceOutAssembly="false",这表示不会将生成器的作为引用,而是将分析器生成的代码,如果将特性定义在生成器中,主项目引用不到特性定义
namespace SourceGeneratorConsole
{[AttributeUsage(AttributeTargets.Method,AllowMultiple =false,Inherited =false)]public sealed class SayHelloAttribute:Attribute; //新语法,特性可以直接使用分号结束
}
  1. 在主项目中声明一个分部方法
namespace SourceGeneratorConsole
{public partial class GreetingUseAttribute{[SayHello]public static partial void SayHi(string name);}
}
  1. 按照上面的流程创建源代码生成器
namespace SourceGeneratorConsole.UseAttributes
{[Generator(LanguageNames.CSharp)]public sealed class GreetingGenerator : ISourceGenerator{public void Execute(GeneratorExecutionContext context){if (context is not { SyntaxContextReceiver: SyntaxContextReceiver { FoundSymbolPairs: var methodSymbols and not [] } }){return;}foreach (var methodSymbol in methodSymbols){//获取对应的class类型var containingType = methodSymbol.ContainingType;//获取完整命名空间名称,包括globalvar namespaceName = containingType.ContainingNamespace.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);var namspaceString = namespaceName["global::".Length..];//查看到底是什么类型var typeKindString = containingType.TypeKind switch{TypeKind.Class => "class",TypeKind.Struct => "struct",TypeKind.Interface => "interface",_ => throw new InvalidOperationException("错误类型")} ;var syntaxNode = (MethodDeclarationSyntax)methodSymbol.DeclaringSyntaxReferences[0].GetSyntax();context.AddSource($"{containingType.Name}.g.cs", $$"""//加上这句话,告知编译器,这个文件是由源代码生成器生成的,//防止编译器进行代码分析,避免不必要的编译器警告//<auto-generated>namespace {{namspaceString}};partial {{typeKindString}} {{containingType.Name}}{{{syntaxNode.Modifiers}} void {{methodSymbol.Name}}(string name){global::System.Console.WriteLine($"Hello, World {name}!");}}""");}}public void Initialize(GeneratorInitializationContext context){context.RegisterForSyntaxNotifications(() => new SyntaxContextReceiver());}}//带有语法上下文的接口,获取所有标记了SayHelloAttribute的方法file sealed class SyntaxContextReceiver : ISyntaxContextReceiver{//表示找到方法的定义信息public List<IMethodSymbol> FoundSymbolPairs { get; } = new();public void OnVisitSyntaxNode(GeneratorSyntaxContext context){//判别当前语法是否为方法//如果是,还要是分部方法//如果满足,获取编译信息和语义信息if (context is not { Node: MethodDeclarationSyntax { Modifiers: var modifiers and not [] } methodSytax, SemanticModel: { Compilation: var compolation } semanticModel }){return;}//上面的替代方式// var node = context.Node;//语法节点// if (node is not MethodDeclarationSyntax methodSyntax)// {//     return;// }// var semanticModel= context.SemanticModel;//具有更多语义信息的模型// var compolation= semanticModel.Compilation;//编译信息if (!modifiers.Any(SyntaxKind.PartialKeyword)){return;}var attribute = compolation.GetTypeByMetadataName("SourceGeneratorConsole.SayHelloAttribute")!;//通过全名称var methodSymbol = semanticModel.GetDeclaredSymbol(methodSytax)!;//获取定义信息//判断是否有特性,要用SymbolEqualityComparer.Default.Equals来进行比较bool hasAttribute = methodSymbol.GetAttributes().Any(e => SymbolEqualityComparer.Default.Equals(e.AttributeClass, attribute));if (!hasAttribute){return;}//方法必须返回void,而且有一个string参数if (methodSymbol is not { ReturnsVoid: true, Parameters: [{ Type.SpecialType:SpecialType.System_String}] }){return;}FoundSymbolPairs.Add(methodSymbol);}}
}
  1. 使用源代码生成器
GreetingUseAttribute.SayHi("使用特性的属性生成器");

image-20231110102223664

06 自定义MyTuble类型实战

我们经常用到Func泛型委托,该泛型委托最多支持16个参数和一个返回值,因为泛型定义没有类似于可变参数的功能,对于不同数量的泛型参数一定要定义同数量的泛型定义。类似于下面这样。

Func<TResult>
Func<T, TResult>
Func<T1, T2, TResult>
Func<T1, T2, T3, TResult>
Func<T1, T2, T3, T4, TResult>
Func<T1, T2, T3, T4, T5, TResult>
Func<T1, T2, T3, T4, T5, T6, TResult>
Func<T1, T2, T3, T4, T5, T6, T7, TResult>
Func<T1, T2, T3, T4, T5, T6, T7, T8, TResult>

我们仿照Func泛型委托自定义一个MyTuple泛型类型

  1. 先定义一个MyTuple模板,这是一个定义了2个泛型参数的MyTuple类型,根据该模板要定义支持多个泛型参数的MyTuple类型
public readonly struct MyTuple<T1, T2>(T1 value1, T2 value2) : IEqualityOperators<MyTuple<T1, T2>, MyTuple<T1, T2>, bool> where T1 : IEqualityOperators<T1, T1, bool> where T2 : IEqualityOperators<T2, T2, bool>
{public T1 Value1 { get; } = value1;public T2 Value2 { get; } = value2;public static bool operator ==(MyTuple<T1, T2> left, MyTuple<T1, T2> right){return left.Value1 == right.Value1 && left.Value2 == right.Value2;}public static bool operator !=(MyTuple<T1, T2> left, MyTuple<T1, T2> right){return !(left == right);}
}
  1. 写一个源代码生成器,根据上面的模板进行改造,自动生成含有1-8个泛型参数的MyTuple类型,其根本原理就是字符串的操作。
[Generator(LanguageNames.CSharp)]
public class MyTupleGenerator : ISourceGenerator
{public void Execute(GeneratorExecutionContext context){var list = new List<string>();for (int i = 2; i <= 8; i++){var indices = Enumerable.Range(1, i).ToArray();var genericArgs = $"<{string.Join(", ", from index in indicesselect $"T{index}" )}>";var ctorArgs = string.Join(", ", from index in indicesselect $"T{index} value{index}");var constraints = string.Join("\r\n\t",from index in indicesselect $"where T{index}: global::System.Numerics.IEqualityOperators<T{index},T{index},bool>");var properties = string.Join("\r\n\t",from index in indicesselect $"public T{index} Value{index} {{ get; }}=value{index};");var comparison = string.Join(" && ", from index in indicesselect $"left.Value{index} == right.Value{index}");list.Add($$"""public readonly struct MyTuple{{genericArgs}}({{ctorArgs}}):global::System.Numerics.IEqualityOperators<MyTuple{{genericArgs}},MyTuple{{genericArgs}},bool>{{constraints}}{{{properties}}public static bool operator ==(MyTuple{{genericArgs}} left, MyTuple{{genericArgs}} right){return {{comparison}};}public static bool operator !=(MyTuple{{genericArgs}} left, MyTuple{{genericArgs}} right){return !(left == right);}}""");}context.AddSource("MyTuple.g.cs", $$"""//<auto-generated/>namespace System;{{string.Join("\r\n\r\n",list)}}""");}public void Initialize(GeneratorInitializationContext context){}
}
  1. 主项目引用源代码生成器后,使用MyTuple
var myTuple1 = new MyTuple<int, double>(1, 3.0);
var myTuple2 = new MyTuple<int, double>(1, 3.0);
var myTuple3 = new MyTuple<int, double,float>(1, 3.0,5.6f);
var myTuple4 = new MyTuple<int, double,float>(1, 3.0,5.6f);
var myTuple5 = new MyTuple<int, double,float,uint>(1, 3.0,5.6f,8);
var myTuple6 = new MyTuple<int, double,float,uint>(1, 3.0,5.6f,7);Console.WriteLine(myTuple2 == myTuple1);
Console.WriteLine(myTuple4 == myTuple3);
Console.WriteLine(myTuple6 == myTuple5);

image-20231110102032761

07AdditionalFiles的使用

上一章节中,我们在直接定义了MyTuple时设置最大泛型参数数量为8,如果我们需要根据需要来设置最大泛型参数数量,则可以在主项目中增加一个配置文件,文件中对此进行设置,并在源代码生成器中使用GeneratorExecutionContext的AdditionalFiles属性来处理非代码文件

  1. 在主项目中增加一个文件,本次案例增加一个MyTupleMaxTypeArgumentCount.txt文件,在该文件中写入4。
  2. 在主项目配置中,增加
<ItemGroup><AdditionalFiles Include="MyTupleMaxTypeArgumentCount.txt"/>
</ItemGroup>
  1. 在06章节中源代码基础上,增加读取本地文件功能
[Generator(LanguageNames.CSharp)]
public class MyTupleGenerator : ISourceGenerator
{public void Execute(GeneratorExecutionContext context){var maxCount = 8;//读取本地文件var additionalFiles = context.AdditionalFiles;if (additionalFiles is [{ Path: var path }]){var result = File.ReadAllText(path);var regex = new Regex(@"\d+");if (regex.Match(result) is { Success:true,Value:var v} && int.TryParse(v,out var value) && value is >=2 and <=8){maxCount = value;}}var list = new List<string>();for (int i = 2; i <= maxCount; i++){......//忽略,参考06章节}......//忽略,参考06章节}
}

08自定义编译器诊断信息

在进行编译时,编译器会自动给出编译信息供用户查看,通常编译器诊断信息如下所示。

由于源代码生成器会自动后台生成,所以给出诊断信息是十分必要的。本章节根据07章节中的章节,给出自定义编译器诊断信息的

[Generator(LanguageNames.CSharp)]
public class MyTupleGenerator : ISourceGenerator
{//首先创建一个DiagnosticDescriptorstatic readonly DiagnosticDescriptor descriptor = new DiagnosticDescriptor("SG0001",//代码,可自定义,格式一般为 两个字母+四位数字"本地配置文件错误","源代码生成器生成成功,但本地配置文件有错误。{0}","SourceGenerator", //此处可以用占位符DiagnosticSeverity.Warning,//提示类别true, "源代码生成器生成成功,但本地配置文件有错误。");public void Execute(GeneratorExecutionContext context){var maxCount = 8;//读取本地文件var additionalFiles = context.AdditionalFiles;if (additionalFiles is [{ Path: var path }]){var result = File.ReadAllText(path);var regex = new Regex(@"\d+");var match = regex.Match(result);if(!match.Success){//给出编译器信息,后面的文字则是在descriptor中流出的占位符context.ReportDiagnostic(Diagnostic.Create(descriptor,Location.None, "配置文件的内容并不是一个数字"));		  //此处不能return,因为此处不成立要使用默认值maxCount = 8,采用goto语句goto nextStep;}var v = match.Value;if (!int.TryParse(v,out var value)){context.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None, "数字过大"));goto nextStep;}if (value is not >=2 and <=8){context.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None, "数字只能在[2,8]"));goto nextStep;}maxCount = value;}//此处利用标签进行跳转nextStep:var list = new List<string>();for (int i = 2; i <= maxCount; i++){......//忽略,参考06章节}......//忽略,参考06章节}
}

随便改一下MyTupleMaxTypeArgumentCount.txt里面的内容为非数字类型,则会收到

image-20231110153622944

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

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

相关文章

4.2每日一题(求多元函数在某一点的微分)

1、分别求x和y的偏导&#xff0c;再相加即可 2、因为多元函数的表达式不方便求偏导&#xff0c;所以可以使用先代后求法&#xff1a; &#xff08;1&#xff09;对x偏导&#xff1a;把y0代入&#xff0c;很容易求出对x偏导的结果 &#xff08;2&#xff09;对y偏导&#xff1a…

caffe搭建squeezenet网络的整套工程

之前用pytorch构建了squeezenet&#xff0c;个人觉得pytorch是最好用的&#xff0c;但是有的工程就是需要caffe结构的&#xff0c;所以本篇也用caffe构建一个squeezenet网络。 数据处理 首先要对数据进行处理&#xff0c;跟pytorch不同&#xff0c;pytorch读取数据只需要给数据…

帝国cms中如何让外部链接直接从新窗口打开页面

<?php if($bqr[isurl]) { ?> <a href"<?$bqsr[titleurl]?>" target"_blank"> <?php } else { ?> <a href"<?$bqsr[titleurl]?>"> <?php } ?>

2023最新最全【Adobe After Effection 2023】下载安装零基础教程【附安装包】

AE2023下载点这里 教学 1.鼠标右击【Ae2023(64bit)】压缩包选择&#xff08;win11系统需先点击“显示更多选项”&#xff09;【解压到 Ae2023(64bit)】。 2.打开解压后的文件夹&#xff0c;鼠标右击【Set-up】选择【以管理员身份运行】。 3.点击【文件夹图标】&#xff0c;…

DNS域名解析

目录 1.概述 1.1产生原因 1.2作用 1.3连接方式 1.4因特网的域名结构 1.4.1拓扑 1.4.2分类 1.4.3域名服务器类型划分 2. DNS域名解析过程 2.1分类 2.2解析图 2.2.2过程分析 3.搭建DNS域名解析服务器 3.1.概述 3.2安装软件 3.3bind服务中三个关键文件 3.4主配置…

PNAS | 蛋白质结构预测屈服于机器学习

今天为大家介绍的是来自James E. Rothman的一篇短文。今年的阿尔伯特拉斯克基础医学研究奖表彰了AlphaFold的发明&#xff0c;这是蛋白质研究历史上的一项革命性进展&#xff0c;首次提供了凭借序列信息就能够准确预测绝大多数蛋白质的三维氨基酸排列的实际能力。这一非凡的成就…

堆排序(大根堆、小根堆)

参考视频&#xff1a; 1、数据结构&#xff0c;小根堆的调整&#xff01;必须熟练掌握&#xff01; 2、数据结构建堆筛选输出最小值 | 计算机软件考研期末知识点2

Scikit-LLM:一款大模型与 scikit-learn 完美结合的工具!

Scikit-LLM 是文本分析领域的一项重大变革&#xff0c;它将像 ChatGPT 这样强大的语言模型与 scikit-learn 相结合&#xff0c;提供了一套无与伦比的工具包&#xff0c;用于理解和分析文本。 有了 scikit-LLM&#xff0c;你可以发现各种类型的文本数据中的隐藏模式、情感和上下…

【STM32】定时器+基本定时器

一、定时器的基本概述 1.软件定时器原理 原来我们使用51单片机的时候&#xff0c;是通过一个__nop()__来进行延时 我们通过软件的方式来进行延时功能是不准确的&#xff0c;受到很多不确定因素。 2.定时器原理&#xff1a;计数之间的比值 因为使用软件延时受到影响&#xff0c…

Spring中的循环依赖解决方案

前言&#xff1a;测试环境突发BeanCurrentlyInCreationException&#xff0c;导致后端服务启动失败&#xff0c;一看就是Spring的Bean管理中循环依赖。项目中存在Bean的循环依赖&#xff0c;是代码质量低下的表现。多数人寄希望于框架层来给擦屁股&#xff0c;造成了整个代码的…

大数据-玩转数据-Flume

一、Flume简介 Flume提供一个分布式的,可靠的,对大数据量的日志进行高效收集、聚集、移动的服务,Flume只能在Unix环境下运行。Flume基于流式架构,容错性强,也很灵活简单。Flume、Kafka用来实时进行数据收集,Spark、Flink用来实时处理数据,impala用来实时查询。二、Flume…

开放领域对话系统架构

开放领域对话系统是指针对非特定领域或行业的对话系统&#xff0c;它可以与用户进行自由的对话&#xff0c;不受特定领域或行业的知识和规则的限制。开放领域对话系统需要具备更广泛的语言理解和生成能力&#xff0c;以便与用户进行自然、流畅的对话。 与垂直领域对话系统相比…

AIX5.3安装weblogic10.3

目录 1安装IBM JDK 1.6 2图形化准备 3安装weblogic 准备 4图形化界面安装 1安装IBM JDK 1.6 1.1检查操作系统 # oslevel 5.3.0.0 # bootinfo -y (显示AIX机器硬件是64位) 64 # bootinfo -K (显示AIX系统内核是64位) 64 因此&#xff0c;系统需要安装64位的jdk&#xff0c;…

JavaWeb Day09 Mybatis-基础操作01-增删改查

目录 环境准备 ①Emp.sql ②Emp.java 一、删除 ①Mapper层 ②测试类 ③预编译SQL&#xff08;查看mybatis日志&#xff09; 1.性能 2.安全 ④总结 二、新增 ①Mapper层 ②测试类 ③结果 ④新增&#xff08;主键返回&#xff09; 1.Mapper层 2.测试类 ⑤总结​…

上机4KNN实验4

目录 编程实现 kNN 算法。一、步骤二、实现代码三、总结知识1、切片2、iloc方法3、归一化4、MinMaxScale&#xff08;&#xff09;5、划分测试集、训练集6、KNN算法 .py 编程实现 kNN 算法。 1、读取excel表格存放的Iris数据集。该数据集有5列&#xff0c;其中前4列是条件属性…

Carla之语义分割及BoundingBox验证模型

参考&#xff1a; Carla系列——4.Cara模拟器添加语义分割相机&#xff08;Semantic segmentation camera&#xff09; Carla自动驾驶仿真五&#xff1a;opencv绘制运动车辆的boudingbox&#xff08;代码详解&#xff09; Carla官网Bounding Boxes Carla官网创建自定义语义标签…

【QT】飞机大战

0 项目简介 飞机大战是我们大家所熟知的一款小游戏&#xff0c;本教程就是教大家如何制作一款自己的飞机大战 首先我们看一下效果图 玩家控制一架小飞机&#xff0c;然后自动发射子弹&#xff0c;如果子弹打到了飞下来的敌机&#xff0c;则射杀敌机&#xff0c;并且有爆炸的特…

隧道施工工艺流程vr线上虚拟展示成为产品3D说明书

行业内都知道&#xff0c;汽车生产的大部分都需要冲压加工来完成&#xff0c;因此汽车冲压工艺是汽车制造过程中的重要环节&#xff0c;传统的展示方式往往局限于二维图纸和实地操作&#xff0c;难以充分展现工艺的细节和流程。然而&#xff0c;随着技术的进步&#xff0c;汽车…

不同优化器的应用

简单用用&#xff0c;优化器具体参考 深度学习中的优化器原理(SGD,SGDMomentum,Adagrad,RMSProp,Adam)_哔哩哔哩_bilibili 收藏版&#xff5c;史上最全机器学习优化器Optimizer汇总 - 知乎 (zhihu.com) import numpy as np import matplotlib.pyplot as plt import torch # …

优秀智慧园区案例 - 中建科技产业园(中建·光谷之星),万字长文解析先进智慧园区建设方案经验

一、项目背景 中建科技产业园&#xff08;中建光谷之星&#xff09;&#xff0c;位于武汉光谷中心城、中国&#xff08;湖北&#xff09;自贸试验区武汉片区双核心区&#xff0c;光谷发展主轴高新大道北侧&#xff0c;建筑面积108万平米&#xff0c;是中建三局“中建之星”和“…