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,静态、partial
    public 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;
        }
        //判断子节点,也就是类型内部的成员是否有partial
        foreach (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;
                //获取完整命名空间名称,包括global
                var 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 indices
                select $"T{index}" )}>";
            var ctorArgs = string.Join(", ", 
                from index in indices
                select $"T{index} value{index}");
            var constraints = string.Join("\r\n\t",
                from index in indices
                select $"where T{index}: global::System.Numerics.IEqualityOperators<T{index},T{index},bool>");
            var properties = string.Join("\r\n\t",
                from index in indices
                select $"public T{index} Value{index} {{ get; }}=value{index};");

            var comparison = string.Join(" && ", from index in indices
                                                 select $"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
{
    //首先创建一个DiagnosticDescriptor
    static 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.mfbz.cn/a/131040.html

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

相关文章

python3GUI--PyQt5打包心得(二)nuitka、inno Setup(详细图文演示、附所有软件)

文章目录 一&#xff0e;前言二&#xff0e;准备1.nuitka1.1介绍1.3项目地址1.3安装 2.mingw641.1介绍1.2下载安装 3.Inno Setup1.1介绍1.2安装 三&#xff0e;nuitka打包1.打包2.装mingw643.装ccahe4.打包完成 四&#xff0e;测试效果五&#xff0e;inno Setup制作安装软件1.配…

微信小程序前端开发

目录 前言&#xff1a; 1. 框架选择和项目搭建 2. 小程序页面开发 3. 数据通信和接口调用 4. 性能优化和调试技巧 5. 小程序发布和上线 前言&#xff1a; 当谈到微信小程序前端开发时&#xff0c;我们指的是使用微信小程序框架进行开发的一种方式。在本文中&#xff0c;我…

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(三)

员工分页查询和账号启用禁用功能 1. 员工分页查询1.1 需求分析和设计1.1.1 产品原型1.1.2 接口设计 1.2 代码开发1.2.1 设计DTO类1.2.2 封装PageResult1.2.3 Controller层1.2.4 Service层接口1.2.5 Service层实现类1.2.6 Mapper层 1.3 功能测试1.4 代码完善 2. 启用禁用员工账号…

FPGA与STM32_FSMC总线通信实验

FPGA与STM32_FSMC总线通信实验 内部存储器IP核的参数设置创建IP核FPGA代码STM32标准库的程序 STM32F407 上自带 FSMC 控制器&#xff0c;通过 FSMC 总线的地址复用模式实现STM32 与 FPGA 之间的通信&#xff0c;FPGA 内部建立 RAM 块&#xff0c;FPGA 桥接 STM32 和 RAM 块&…

Python喜羊羊

目录 系列文章 写在前面 绘图基础 画喜羊羊 写在后面 系列文章 序号文章目录直达链接表白系列1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want5…

Linux常用命令——bzmore命令

在线Linux命令查询工具 bzmore 查看bzip2压缩过的文本文件的内容 补充说明 bzmore命令用于查看bzip2压缩过的文本文件的内容&#xff0c;当下一屏显示不下时可以实现分屏显示。 语法 bzmore(参数)参数 文件&#xff1a;指定要分屏显示的.bz2压缩包。 在线Linux命令查询…

AD9371 Crossbar 和 I、Q数据 映射JESD204B传输层

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 AD9371 官方例程 时钟间的关系与生成 &#xff1a; AD9371 官方…

Win10文件资源管理器卡顿不流畅的解决方法

在Win10电脑中&#xff0c;用户点击打开文件资源管理器&#xff0c;发现文件资源管理器变得卡顿不流畅了&#xff0c;非常影响用户的操作效率。接下来小编给大家带来了最简单的解决方法&#xff0c;解决后用户再去操作Win10系统的文件资源管理器&#xff0c;就会发现变得顺畅不…

主成分分析法(PCA)的理解(附python代码案例)

目录 一、PCA简介二、举个例子三、计算过程&#xff08;公式&#xff09;3.0 题干假设3.1 标准化3.2 计算协方差矩阵3.3 计算特征值和特征值向量3.3 多重共线性检验&#xff08;可跳过&#xff09;3.4 适合性检验&#xff08;可跳过&#xff09;3.5 计算主成分贡献率及累计贡献…

第七章 :Spring Boot web开发常用注解(二)

第七章 :Spring Boot web开发常用注解(二) 前言 本章节知识重点:作者结合自身开发经验,以及觉察到的一个现象:Springboot注解全面理解和掌握的并不多,对注解进行了全面总结,共分两个章节,可以作为web开发工程师注解参考手册,SpringBoot常用注解大全,一目了然!。本…

【码银送书第十期】《强化学习:原理与Python实战》

目录 1.什么是人工智能对齐 2.为什么要研究人工智能对齐 3.人工智能对齐的常见方法 1.什么是人工智能对齐 人工智能对齐&#xff08;AI Alignment&#xff09;指让人工智能的行为符合人的意图和价值观。 人工智能系统可能会出现“不对齐”&#xff08;misalign&#xff09;的…

助力细胞分选,“量身定做”您的磁珠

免疫磁珠因结合了固化试剂特有的优点与免疫学反应的高度特异性而渗透到病理、生理、药理、微生物、生化以及分子遗传学等各个领域。其中&#xff0c;随着细胞分选技术的不断发展&#xff0c;免疫磁珠细胞分选技术已越来越受到研究者的认可&#xff0c;磁珠细胞分选可以从异质细…

linux:使用nc(netcat)命令进行端口检测,并使用Docker管理容器

需求&#xff1a; 循环检测IP:端口是否能正常连接&#xff0c;能连接则关闭docker服务&#xff0c;不能连接则开启docker服务实现&#xff1a;  &esmp;通过创建linux可执行shell脚本文件&#xff0c;再设置crontab调度执行实现上述需求。详细步骤如下&#xff1a; 创建sh…

matlab中的iddata函数的初步理解和使用程序举例

matlab中的iddata函数的初步理解和程序举例 一、iddata函数功能 iddata函数常用于系统识别分析领域数据分析方面。该函数在时域或频域中&#xff0c;将用于系统识别的输入输出数据及其特性数据的生成对象数据类型。即&#xff0c;可以使用iddata函数封装要标识的系统的输入和…

文件扩展名批量修改:txt文件扩展名批量修改为doc文档,高效办公的方法

在我们的日常工作中&#xff0c;经常需要处理大量的文本文件&#xff0c;这些文件可能以.txt为扩展名&#xff0c;而我们需要将其修改为.doc扩展名以方便进一步的操作。这种情况下&#xff0c;我们引用云炫文件管理器来将扩展名批量修改&#xff0c;提升办公的效率。在进行文件…

万物皆数——用matlab求解二阶微分方程

一、背景 毕达哥拉斯的“万物皆数”哲学观点表达了一个理念&#xff0c;即宇宙万物都可以通过数学语言来描述&#xff0c;数是万物的本原。 勾股定理就是毕达哥拉斯提出&#xff0c;因此在西方勾股定理也被叫做毕达哥拉斯定理。 工科类的专业&#xff0c;越到后面越感觉到数学…

从开源项目聊鱼眼相机的“360全景拼接”

目录 概述 从360全景的背景讲起 跨过参数标定聊透视变化 拼接图片后处理 参考文献 概述 写这篇文章的原因完全源于开源项目(GitHub参阅参考文献1)。该项目涵盖了环视系统的较为全貌的制作过程&#xff0c;包含完整的标定、投影、拼接和实时运行流程。该篇文章主要是梳理全…

Wincc flexible SMART v4 报警蜂鸣器的基本使用方法示例

Wincc flexible SMART v4 报警蜂鸣器的基本使用方法示例 WinCC flexible SMART V4 SP1 软件针对SMART LINE V4 面板新增了触发蜂鸣器报警功能,但要注意该功能仅支持固件版本为 4.0.1.0 及以上的设备。 可通过配置以下两个系统函数来触发蜂鸣器: 举例说明: 组态离散量报警,在…

xss 通过秘籍

终极测试代码 <sCr<ScRiPt>IPT>OonN"\/(hrHRefEF)</sCr</ScRiPt>IPT> 第一关&#xff08;没有任何过滤&#xff09; 使用终极测试代码&#xff0c;查看源码 发现没有任何过滤&#xff0c;直接使用javascrupt中的alert弹框 <script>aler…

树之二叉排序树(二叉搜索树)

什么是排序树 说一下普通二叉树可不是左小右大的 插入的新节点是以叶子形式进行插入的 二叉排序树的中序遍历结果是一个升序的序列 下面是两个典型的二叉排序树 二叉排序树的操作 构造树的过程即是对无序序列进行排序的过程。 存储结构 通常采用二叉链表作为存储结构 不能 …