【微软技术栈】C#.NET 依赖项注入
本文内容
- 多个构造函数发现规则
- 使用扩展方法注册服务组
- 框架提供的服务
- 服务生存期
- 服务注册方法
- 作用域验证
- 范围场景
.NET 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖项之间实现控制反转 (IoC) 的技术。 .NET 中的依赖关系注入是框架的内置部分,与配置、日志记录和选项模式一样。
依赖项是指另一个对象所依赖的对象。 使用其他类所依赖的 Write 方法检查以下 MessageWriter 类:
public class MessageWriter
{public void Write(string message){Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");}
}
类可以创建 MessageWriter 类的实例,以便利用其 Write 方法。 在以下示例中,MessageWriter 类是 Worker 类的依赖项:
public class Worker : BackgroundService
{private readonly MessageWriter _messageWriter = new();protected override async Task ExecuteAsync(CancellationToken stoppingToken){while (!stoppingToken.IsCancellationRequested){_messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");await Task.Delay(1_000, stoppingToken);}}
}
该类创建并直接依赖于 MessageWriter 类。 硬编码的依赖项(如前面的示例)会产生问题,应避免使用,原因如下:
- 要用不同的实现替换
MessageWriter,必须修改Worker类。 - 如果
MessageWriter具有依赖项,则必须由Worker类对其进行配置。 在具有多个依赖于MessageWriter的类的大型项目中,配置代码将分散在整个应用中。 - 这种实现很难进行单元测试。 应用需使用模拟或存根
MessageWriter类,而该类不能使用此方法。
依赖关系注入通过以下方式解决了这些问题:
- 使用接口或基类将依赖关系实现抽象化。
- 在服务容器中注册依赖关系。 .NET 提供了一个内置的服务容器 IServiceProvider。 服务通常在应用启动时注册,并追加到 IServiceCollection。 添加所有服务后,可以使用 BuildServiceProvider 创建服务容器。
- 将服务注入到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时将其释放。
例如,IMessageWriter 接口定义 Write 方法:
namespace DependencyInjection.Example;public interface IMessageWriter
{void Write(string message);
}
此接口由具体类型 MessageWriter 实现:
namespace DependencyInjection.Example;public class MessageWriter : IMessageWriter
{public void Write(string message){Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");}
}
示例代码使用具体类型 MessageWriter 注册 IMessageWriter 服务。 AddSingleton 方法使用单一实例生存期(应用的生存期)注册服务。 本文后面将介绍服务生存期。
using DependencyInjection.Example;HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();using IHost host = builder.Build();host.Run();
在上面的代码中,示例应用:
-
创建主机应用生成器实例。
-
通过注册以下内容来配置服务:
Worker作为托管服务。IMessageWriter接口作为具有MessageWriter类相应实现的单一实例服务。
-
生成主机并运行它。
主机包含依赖关系注入服务提供程序。 它还包含自动实例化 Worker 并提供相应的 IMessageWriter 实现作为参数所需的所有其他相关服务。
namespace DependencyInjection.Example;public sealed class Worker : BackgroundService
{private readonly IMessageWriter _messageWriter;public Worker(IMessageWriter messageWriter) =>_messageWriter = messageWriter;protected override async Task ExecuteAsync(CancellationToken stoppingToken){while (!stoppingToken.IsCancellationRequested){_messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");await Task.Delay(1_000, stoppingToken);}}
}
通过使用 DI 模式,辅助角色服务:
- 不使用具体类型
MessageWriter,只使用实现它的IMessageWriter接口。 这样可以轻松地更改辅助角色服务使用的实现,而无需修改辅助角色服务。 - 不要创建
MessageWriter的实例。 该实例由 DI 容器创建。
可以通过使用内置日志记录 API 来改善 IMessageWriter 接口的实现:
namespace DependencyInjection.Example;public class LoggingMessageWriter : IMessageWriter
{private readonly ILogger<LoggingMessageWriter> _logger;public LoggingMessageWriter(ILogger<LoggingMessageWriter> logger) =>_logger = logger;public void Write(string message) =>_logger.LogInformation("Info: {Msg}", message);
}
更新的 AddSingleton 方法注册新的 IMessageWriter 实现:
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
HostApplicationBuilder (builder)类型是Microsoft.Extensions.Hosting NuGet 包的一部分。
LoggingMessageWriter 依赖于 ILogger<TCategoryName>,并在构造函数中对其进行请求。 ILogger<TCategoryName> 是ILogger<TCategoryName>。
以链式方式使用依赖关系注入并不罕见。 每个请求的依赖关系相应地请求其自己的依赖关系。 容器解析图中的依赖关系并返回完全解析的服务。 必须被解析的依赖关系的集合通常被称为“依赖关系树”、“依赖关系图”或“对象图”。
容器通过利用(泛型)开放类型解析 ILogger<TCategoryName>,而无需注册每个(泛型)构造类型。
在依赖项注入术语中,服务:
- 通常是向其他对象提供服务的对象,如
IMessageWriter服务。 - 与 Web 服务无关,尽管服务可能使用 Web 服务。
框架提供可靠的日志记录系统。 编写上述示例中的 IMessageWriter 实现来演示基本的 DI,而不是来实现日志记录。 大多数应用都不需要编写记录器。 下面的代码展示了如何使用默认日志记录,只需要将Worker注册为托管服务AddHostedService:
public class Worker : BackgroundService
{private readonly ILogger<Worker> _logger;public Worker(ILogger<Worker> logger) =>_logger = logger;protected override async Task ExecuteAsync(CancellationToken stoppingToken){while (!stoppingToken.IsCancellationRequested){_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);await Task.Delay(1_000, stoppingToken);}}
}
使用前面的代码时,无需更新 Program.cs,因为框架提供日志记录。
1、多个构造函数发现规则
当某个类型定义多个构造函数时,服务提供程序具有用于确定要使用哪个构造函数的逻辑。 选择最多参数的构造函数,其中的类型是可 DI 解析的类型。 请考虑以下 C# 示例服务:
public class ExampleService
{public ExampleService(){}public ExampleService(ILogger<ExampleService> logger){// omitted for brevity}public ExampleService(FooService fooService, BarService barService){// omitted for brevity}
}
在前面的代码中,假定已添加日志记录,并且可以从服务提供程序解析,但 FooService 和 BarService 类型不可解析。 使用 ILogger<ExampleService> 参数的构造函数用于解析 ExampleService 实例。 即使有定义多个参数的构造函数,FooService 和 BarService 类型也不能进行 DI 解析。
如果发现构造函数时存在歧义,将引发异常。 请考虑以下 C# 示例服务:
public class ExampleService
{public ExampleService(){}public ExampleService(ILogger<ExampleService> logger){// omitted for brevity}public ExampleService(IOptions<ExampleOptions> options){// omitted for brevity}
}
警告
具有不明确的可 DI 解析的类型参数的 ExampleService 代码将引发异常。 不要执行此操作,它旨在显示“不明确的可 DI 解析类型”的含义。
在前面的示例中,有三个构造函数。 第一个构造函数是无参数的,不需要服务提供商提供的服务。 假设日志记录和选项都已添加到 DI 容器,并且是可 DI 解析的服务。 当 DI 容器尝试解析 ExampleService 类型时,将引发异常,因为这两个构造函数不明确。
可通过定义一个接受 DI 可解析的类型的构造函数来避免歧义:
public class ExampleService
{public ExampleService(){}public ExampleService(ILogger<ExampleService> logger,IOptions<ExampleOptions> options){// omitted for brevity}
}
2、使用扩展方法注册服务组
Microsoft 扩展使用一种约定来注册一组相关服务。 约定使用单个 Add{GROUP_NAME} 扩展方法来注册该框架功能所需的所有服务。 例如,AddOptions 扩展方法会注册使用选项所需的所有服务。
3、框架提供的服务
使用任何可用的主机或应用生成器模式时,会应用默认值,并由框架注册服务。 请考虑一些最常用的主机和应用生成器模式:
- Host.CreateDefaultBuilder()
- Host.CreateApplicationBuilder()
- WebHost.CreateDefaultBuilder()
- WebApplication.CreateBuilder()
- WebAssemblyHostBuilder.CreateDefault
- MauiApp.CreateBuilder
从这些 API 中的任何一个创建生成器后, IServiceCollection具有框架定义的服务,具体取决于主机的配置方式。 对于基于 .NET 模板的应用,该框架会注册数百个服务。
下表列出了框架注册的这些服务的一小部分:
| 服务类型 | 生存期 |
|---|---|
| Microsoft.Extensions.DependencyInjection.IServiceScopeFactory | 单例 |
| IHostApplicationLifetime | 单例 |
| Microsoft.Extensions.Logging.ILogger<TCategoryName> | 单例 |
| Microsoft.Extensions.Logging.ILoggerFactory | 单例 |
| Microsoft.Extensions.ObjectPool.ObjectPoolProvider | 单例 |
| Microsoft.Extensions.Options.IConfigureOptions<TOptions> | 暂时 |
| Microsoft.Extensions.Options.IOptions<TOptions> | 单例 |
| System.Diagnostics.DiagnosticListener | 单例 |
| System.Diagnostics.DiagnosticSource | 单例 |
4、服务生存期
可以使用以下任一生存期注册服务:
- 暂时
- 作用域
- 单例
下列各部分描述了上述每个生存期。 为每个注册的服务选择适当的生存期。
4.1 暂时
暂时生存期服务是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。 向 AddTransient 注册暂时性服务。
在处理请求的应用中,在请求结束时会释放暂时服务。
4.2 范围内
对于 Web 应用,指定了作用域的生存期指明了每个客户端请求(连接)创建一次服务。 向 AddScoped 注册范围内服务。
在处理请求的应用中,在请求结束时会释放有作用域的服务。
使用 Entity Framework Core 时,默认情况下 AddDbContext 扩展方法使用范围内生存期来注册 DbContext 类型。
备注
不要从单一实例解析限定范围的服务,并小心不要间接地这样做,例如通过暂时性服务。 当处理后续请求时,它可能会导致服务处于不正确的状态。 可以:
- 从范围内或暂时性服务解析单一实例服务。
- 从其他范围内或暂时性服务解析范围内服务。
默认情况下在开发环境中,从具有较长生存期的其他服务解析服务将引发异常。
4.3 单例
创建单例生命周期服务的情况如下:
- 在首次请求它们时进行创建;或者
- 在向容器直接提供实现实例时由开发人员进行创建。 很少用到此方法。
来自依赖关系注入容器的服务实现的每一个后续请求都使用同一个实例。 如果应用需要单一实例行为,则允许服务容器管理服务的生存期。 不要实现单一实例设计模式,或提供代码来释放单一实例。 服务永远不应由解析容器服务的代码释放。 如果类型或工厂注册为单一实例,则容器自动释放单一实例。
向 AddSingleton 注册单一实例服务。 单一实例服务必须是线程安全的,并且通常在无状态服务中使用。
在处理请求的应用中,当应用关闭并释放 ServiceProvider 时,会释放单一实例服务。 由于应用关闭之前不释放内存,因此请考虑单一实例服务的内存使用。
5、服务注册方法
框架提供了适用于特定场景的服务注册扩展方法:
| 方法 | 自动 对象 释放 | 多种 实现 | 传递参数 |
|---|---|---|---|
Add{LIFETIME}<{SERVICE}, {IMPLEMENTATION}>()示例: services.AddSingleton<IMyDep, MyDep>(); | 是 | 是 | 否 |
Add{LIFETIME}<{SERVICE}>(sp => new {IMPLEMENTATION})示例: services.AddSingleton<IMyDep>(sp => new MyDep());services.AddSingleton<IMyDep>(sp => new MyDep(99)); | 是 | 是 | 是 |
Add{LIFETIME}<{IMPLEMENTATION}>()示例: services.AddSingleton<MyDep>(); | 是 | No | 否 |
AddSingleton<{SERVICE}>(new {IMPLEMENTATION})示例: services.AddSingleton<IMyDep>(new MyDep());services.AddSingleton<IMyDep>(new MyDep(99)); | 否 | 是 | 是 |
AddSingleton(new {IMPLEMENTATION})示例: services.AddSingleton(new MyDep());services.AddSingleton(new MyDep(99)); | 否 | No | 是 |
仅使用实现类型注册服务等效于使用相同的实现和服务类型注册该服务。 因此,我们不能使用捕获显式服务类型的方法来注册服务的多个实现。 这些方法可以注册服务的多个实例,但它们都具有相同的实现类型 。
上述任何服务注册方法都可用于注册同一服务类型的多个服务实例。 下面的示例以 IMessageWriter 作为服务类型调用 AddSingleton 两次。 第二次对 AddSingleton 的调用在解析为 IMessageWriter 时替代上一次调用,在通过 IEnumerable<IMessageWriter> 解析多个服务时添加到上一次调用。 通过 IEnumerable<{SERVICE}> 解析服务时,服务按其注册顺序显示。
using ConsoleDI.IEnumerableExample;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);builder.Services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
builder.Services.AddSingleton<IMessageWriter, LoggingMessageWriter>();
builder.Services.AddSingleton<ExampleService>();using IHost host = builder.Build();_ = host.Services.GetService<ExampleService>();await host.RunAsync();
前面的示例源代码注册了 IMessageWriter 的两个实现。
using System.Diagnostics;namespace ConsoleDI.IEnumerableExample;public sealed class ExampleService
{public ExampleService(IMessageWriter messageWriter,IEnumerable<IMessageWriter> messageWriters){Trace.Assert(messageWriter is LoggingMessageWriter);var dependencyArray = messageWriters.ToArray();Trace.Assert(dependencyArray[0] is ConsoleMessageWriter);Trace.Assert(dependencyArray[1] is LoggingMessageWriter);}
}
ExampleService 定义两个构造函数参数:一个是 IMessageWriter,另一个是 IEnumerable<IMessageWriter>。 第一个 IMessageWriter 是已注册的最后一个实现,而 IEnumerable<IMessageWriter> 表示所有已注册的实现。
框架还提供 TryAdd{LIFETIME} 扩展方法,只有当尚未注册某个实现时,才注册该服务。
在下面的示例中,对 AddSingleton 的调用会将 ConsoleMessageWriter 注册为 IMessageWriter的实现。 对 TryAddSingleton 的调用没有任何作用,因为 IMessageWriter 已有一个已注册的实现:
services.AddSingleton<IMessageWriter, ConsoleMessageWriter>();
services.TryAddSingleton<IMessageWriter, LoggingMessageWriter>();
TryAddSingleton 不起作用,因为已添加它并且“try”将失败。 ExampleService 将断言以下内容:
public class ExampleService
{public ExampleService(IMessageWriter messageWriter,IEnumerable<IMessageWriter> messageWriters){Trace.Assert(messageWriter is ConsoleMessageWriter);Trace.Assert(messageWriters.Single() is ConsoleMessageWriter);}
}
有关详细信息,请参阅:
- TryAdd
- TryAddTransient
- TryAddScoped
- TryAddSingleton
TryAddEnumerable(ServiceDescriptor) 方法仅会在没有同一类型实现的情况下才注册该服务。 多个服务通过 IEnumerable<{SERVICE}> 解析。 注册服务时,如果还没有添加相同类型的实例,就添加一个实例。 库作者使用 TryAddEnumerable 来避免在容器中注册一个实现的多个副本。
在下面的示例中,对 TryAddEnumerable 的第一次调用会将 MessageWriter 注册为 IMessageWriter1的实现。 第二次调用向 IMessageWriter2 注册 MessageWriter。 第三次调用没有任何作用,因为 IMessageWriter1 已有一个 MessageWriter 的已注册的实现:
public interface IMessageWriter1 { }
public interface IMessageWriter2 { }public class MessageWriter : IMessageWriter1, IMessageWriter2
{
}services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter2, MessageWriter>());
services.TryAddEnumerable(ServiceDescriptor.Singleton<IMessageWriter1, MessageWriter>());
服务注册通常与顺序无关,除了注册同一类型的多个实现时。
IServiceCollection 是 ServiceDescriptor 对象的集合。 以下示例演示如何通过创建和添加 ServiceDescriptor 来注册服务:
string secretKey = Configuration["SecretKey"];
var descriptor = new ServiceDescriptor(typeof(IMessageWriter),_ => new DefaultMessageWriter(secretKey),ServiceLifetime.Transient);services.Add(descriptor);
内置 Add{LIFETIME} 方法使用同一种方式。
5.1 构造函数注入行为
服务可使用以下方式来解析:
- IServiceProvider
- ActivatorUtilities:
- 创建未在容器中注册的对象。
- 用于某些框架功能。
构造函数可以接受非依赖关系注入提供的参数,但参数必须分配默认值。
当服务由 IServiceProvider 或 ActivatorUtilities 解析时,构造函数注入需要 public 构造函数。
当服务由 ActivatorUtilities 解析时,构造函数注入要求只存在一个适用的构造函数。 支持构造函数重载,但其参数可以全部通过依赖注入来实现的重载只能存在一个。
6、作用域验证
如果应用在Development环境中运行,并调用CreateApplicatioBuilder以生成主机,默认服务提供程序会执行检查,以确认以下内容:
- 没有从根服务提供程序解析到范围内服务。
- 未将范围内服务注入单一实例。
调用 BuildServiceProvider 时创建根服务提供程序。 在启动提供程序和应用时,根服务提供程序的生存期对应于应用的生存期,并在关闭应用时释放。
有作用域的服务由创建它们的容器释放。 如果范围内服务创建于根容器,则该服务的生存期实际上提升至单一实例,因为根容器只会在应用关闭时将其释放。 验证服务作用域,将在调用 BuildServiceProvider 时收集这类情况。
7、范围场景
IServiceScopeFactory 始终注册为单一实例,但 IServiceProvider 可能因包含类的生存期而异。 例如,如果从某个范围解析服务,而这些服务中的任意一种采用 IServiceProvider,该服务将是区分范围的实例。
若要在 IHostedService 的实现(例如 BackgroundService)中实现范围服务,请不要通过构造函数注入来注入服务依赖项。 请改为注入 IServiceScopeFactory,创建范围,然后从该范围解析依赖项以使用适当的服务生存期。
namespace WorkerScope.Example;public sealed class Worker : BackgroundService
{private readonly ILogger<Worker> _logger;private readonly IServiceScopeFactory _serviceScopeFactory;public Worker(ILogger<Worker> logger, IServiceScopeFactory serviceScopeFactory) =>(_logger, _serviceScopeFactory) = (logger, serviceScopeFactory);protected override async Task ExecuteAsync(CancellationToken stoppingToken){while (!stoppingToken.IsCancellationRequested){using (IServiceScope scope = _serviceScopeFactory.CreateScope()){try{_logger.LogInformation("Starting scoped work, provider hash: {hash}.",scope.ServiceProvider.GetHashCode());var store = scope.ServiceProvider.GetRequiredService<IObjectStore>();var next = await store.GetNextAsync();_logger.LogInformation("{next}", next);var processor = scope.ServiceProvider.GetRequiredService<IObjectProcessor>();await processor.ProcessAsync(next);_logger.LogInformation("Processing {name}.", next.Name);var relay = scope.ServiceProvider.GetRequiredService<IObjectRelay>();await relay.RelayAsync(next);_logger.LogInformation("Processed results have been relayed.");var marked = await store.MarkAsync(next);_logger.LogInformation("Marked as processed: {next}", marked);}finally{_logger.LogInformation("Finished scoped work, provider hash: {hash}.{nl}",scope.ServiceProvider.GetHashCode(), Environment.NewLine);}}}}
}
在上述代码中,当应用运行时,后台服务:
- 依赖于 IServiceScopeFactory。
- 创建 IServiceScope 用于解析其他服务。
- 解析区分范围内的服务以供使用。
- 处理要处理的对象,然后对其执行中继操作,最后将其标记为已处理。
在示例源代码中,可以看到 IHostedService 的实现如何从区分范围的服务生存期中获益。
相关文章:
【微软技术栈】C#.NET 依赖项注入
本文内容 多个构造函数发现规则使用扩展方法注册服务组框架提供的服务服务生存期服务注册方法作用域验证范围场景 .NET 支持依赖关系注入 (DI) 软件设计模式,这是一种在类及其依赖项之间实现控制反转 (IoC) 的技术。 .NET 中的依赖关系注入是框架的内置部分&#…...
评国青、优青、杰青,到底需要什么级别的文章?五篇代表作如何选?
一到年底就听同事们讨论到底申报“杰青”、“优青”还是“国青”,那么,“杰青”到底是什么呢?它和“优青”、“国青”又有什么区别呢? 杰青,全称“国家杰出青年基金获得者”,是国家自然科学基金里人才资助…...
使用双动态令牌混合器学习全局和局部动态以进行视觉识别
TransXNet: Learning Both Global and Local Dynamics with a Dual Dynamic Token Mixer for Visual Recognition 1、问题与解决2、引言3、方法3.1 双动态令牌混合器(D- Mixer)3.2 IDConv(Input-dependent Depthwise Convolution)3.3 Overlapping Spatial Reduction Attention …...
Flutter笔记 - 关于 fit 属性以及相关知识的总结
Flutter笔记 关于 fit 属性以及相关知识的总结 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/13434451…...
如何在PPT中去除编辑密码?
:忘记PPT幻灯片密码?最简单的办法在这里! 【摘要】:具体步骤如下:第一步百度搜索【密码帝官网】,第二步点击“立即开始”,在用户中心上传文件即可。不用下载软件,手机电脑都可以用。…...
Kotlin库实现多线程爬取数据
由于字数限制,以下是一个简化版的爬虫程序示例,使用了Kotlin的网络库kotlinx.coroutines和kotlinx.html。这个程序会爬取一个简单的Python多线程跑数据的网页,并打印出结果。 import kotlinx.coroutines.* import kotlinx.html.* import java…...
RT-Thread Env使用
Env用户手册 Env是RT-Thread推出的开发辅助工具,针对基于RT-Thread操作系统的项目工程,提供编译构建环境、图形化系统配置及软件包管理功能。 其内置的menuconfig提供了简单易用的配置裁剪工具,可对内核、组件和软件包进行自由裁剪…...
2011年09月21日 Go生态洞察:Go图像处理包
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...
《QT从基础到进阶·十七》QCursor鼠标的不同位置坐标获取
一些常用鼠标图形: 鼠标光标相对于整个电脑屏幕的位置:QCursor::pos() 当前光标相对于当前窗口的位置:this->mapFromGlobal(QCursor::pos()) void MainWindow::mouseReleaseEvent(QMouseEvent* event) {QPoint pos event->pos(); …...
K8s----资源管理
目录 一、Secret 1、创建 Secret 1.1 用kubectl create secret命令创建Secret 1.2 内容用 base64 编码,创建Secret 2、使用方式 2.1 将 Secret 挂载到 Volume 中,以 Volume 的形式挂载到 Pod 的某个目录下 2.2 将 Secret 导出到环境变量中 二、Co…...
java.net.UnknownServiceException: CLEARTEXT communication to 127.0.0.1 not p
解决方案3(推荐) 在 AndroidManifest.xml —> application节点中增加 <application...android:usesCleartextTraffic"true"... />...
STM32——系统时钟(概述,问题总结)
文章目录 前言系统时钟1.为什么32有那么多时钟源?2.主频有什么用?3.时钟与主频的关系?4.STM32提供了四种可选择的时钟源头。5.独立看门狗的时钟源只能是 LSI。 前言 系统时钟后续会出一个详细的关于STM32F103的系统时钟分析,可以关注一下。…...
魔众文库系统 v5.5.0 批量快捷上传,文档图标优化,档转换逻辑优化
魔众文库系统基于文档系统知识,建立平台与领域,打造流量、用户、付费和变现的闭环,帮助您更好的搭建文库系统。 魔众文库系统发布v5.5.0版本,新功能和Bug修复累计14项,批量快捷上传,文档图标优化ÿ…...
52. 携带研究材料
题目描述 小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间࿰…...
局域网内部服务器访问外部网络
一、环境说明 如下图所示,局域网1中的服务器是可以访问外网的,局域网2中的服务器发出的数据包经过中间路由可以到达局域网1中的服务器。现在有一种需求需要使局域网2中的服务器也要能访问外网,这里考虑采用如下方法来实现。 二、软…...
IP行业API助力于网络分析和数据挖掘
引言 在当今数字化时代,数据成为了企业、科研机构和政府决策者的重要资源,而IP行业API则成为了数据分析及挖掘的工具之一。IP行业API是一种能够查询IP地址所属的行业分类信息的应用程序接口,它能够提供在网络分析、用户行为分析及大数据挖掘…...
Azure 机器学习 - 如何使用模板创建安全工作区
目录 先决条件了解模板配置模板连接到工作区疑难解答错误:Windows 计算机名的长度不能超过 15 个字符,并且不能全为数字或包含以下字符 本教程介绍如何使用 [Microsoft Bicep]和 [Hashicorp Terraform]模板创建以下 Azure 资源: Azure 虚拟网…...
可变类与不可变类
可变类(Mutable Class)和不可变类(Immutable Class)是面向对象编程中的两种类的设计模式,它们在对象的状态和行为上有不同的特性。 可变类(Mutable Class): 状态可修改:…...
什么是Node.js的调试器(debugger)工具?
聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...
log4j CVE-2021-44228 RCE漏洞复现
一、漏洞特征 Apache Log4j 是 Apache 的一个开源项目,Apache Log4j2是一个基于Java的日志记录工具。该工具重写了Log4j框架,并且引入了大量丰富的特性。我们可以控制日志信息输送的目的地为控制台、文件、GUI组件等,通过定义每一条日志信息的…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...
如何做好一份技术文档?从规划到实践的完整指南
如何做好一份技术文档?从规划到实践的完整指南 🌟 嗨,我是IRpickstars! 🌌 总有一行代码,能点亮万千星辰。 🔍 在技术的宇宙中,我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...
作为点的对象CenterNet论文阅读
摘要 检测器将图像中的物体表示为轴对齐的边界框。大多数成功的目标检测方法都会枚举几乎完整的潜在目标位置列表,并对每一个位置进行分类。这种做法既浪费又低效,并且需要额外的后处理。在本文中,我们采取了不同的方法。我们将物体建模为单…...
Excel 怎么让透视表以正常Excel表格形式显示
目录 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总 1、创建数据透视表 2、设计 》报表布局 》以表格形式显示 3、设计 》分类汇总 》不显示分类汇总...
npm install 相关命令
npm install 相关命令 基本安装命令 # 安装 package.json 中列出的所有依赖 npm install npm i # 简写形式# 安装特定包 npm install <package-name># 安装特定版本 npm install <package-name><version>依赖类型选项 # 安装为生产依赖(默认&…...
