【微软技术栈】C#.NET 依赖项注入

本文内容

  1. 多个构造函数发现规则
  2. 使用扩展方法注册服务组
  3. 框架提供的服务
  4. 服务生存期
  5. 服务注册方法
  6. 作用域验证
  7. 范围场景

.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 的实现如何从区分范围的服务生存期中获益。

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

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

相关文章

LabVIEW在OPC中使用基金会现场总线

LabVIEW在OPC中使用基金会现场总线 本文讨论了如何使用开放的OPC&#xff08;用于过程控制的OLE&#xff09;接口访问基金会现场总线网络和设备。 NI-FBUS通信管理器随附了一个OPC数据访问服务器。 &#xff08;NI-FBUS Configurator自动包含NI-FBUS通信管理器。&#xff09…

Visual Studio2010保姆式安装教程(VS2010 旗舰版),以及如何运行第一个C语言程序,超详细

安装前请关闭杀毒软件&#xff0c;系统防火墙&#xff0c;断开网络连接 参考链接&#xff1a;请点击 下载链接&#xff1a; 通过百度网盘分享的文件&#xff1a;VS2010.zip 链接:https://pan.baidu.com/s/1yQUUCxMJP7FMaistFX94SQ 提取码:96ga 复制这段内容打开「百度网盘APP …

C++ 数组学习资料

C 数组学习资料 目录 什么是数组&#xff1f;声明和初始化数组访问数组元素多维数组数组和指针常见的数组操作数组的限制和注意事项 什么是数组&#xff1f; 在 C 中&#xff0c;数组是一种用于存储相同类型元素的数据结构。它是一个固定大小的连续内存块&#xff0c;每个元…

Linux下的调试工具——GDB

GDB 1.什么是GDB GDB 是由 GNU 软件系统社区提供的调试工具&#xff0c;同 GCC 配套组成了一套完整的开发环境&#xff0c;GDB 是 Linux 和许多 类Unix系统的标准开发环境。 一般来说&#xff0c;GDB 主要能够提供以下四个方面的帮助&#xff1a; 启动程序&#xff0c;可以按…

GF0-57CQD-002 测量参数:加速度、速度、位移–现场可配置

GF0-57CQD-002 测量参数:加速度、速度、位移–现场可配置 GF0-57CQD-002 是一款创新的双通道变送器&#xff0c;专为精确的振动测量而设计。它激励并读取来自加速度计的信号&#xff0c;并将整体振动值作为电流/电压信号传输。它测量加速度、速度和位移等不同参数的振动。配置…

vue3使用vuex的示例(模块化功能)

目录 1. store/index.ts 2. main.ts 3. App.vue调用 4. 如果删除moduleA的namespaced属性, 保留moduleB的namespaced:true 5. 则App.vue修改为: 1. store/index.ts 注意: 需要使用时带上模块名称的namespaced必须为true, 不写或者为false时调用时不需要写模块名称(获取st…

模电学习路径--google镜像chatgpt

交流通路实质 列出电路方程1&#xff0c;方程1对时刻t做微分 所得方程1‘ 即为 交流通路 方程1对时刻t做微分&#xff1a;两个不同时刻的方程1相减&#xff0c;并 令两时刻差为 无穷小 微分 改成 差 模电学习路径&#xff1a; 理论 《电路原理》清华大学 于歆杰 朱桂萍 陆文…

【数据结构】二叉树的遍历递归算法详解

二叉树的遍历 &#x1f4ab;二叉树的结点结构定义&#x1f4ab;创建一个二叉树结点&#x1f4ab;在主函数中手动创建一颗二叉树&#x1f4ab;二叉树的前序遍历&#x1f4ab;调用栈递归——实现前序遍历&#x1f4ab;递归实现中序和后序遍历 &#x1f4ab;二叉树的结点结构定义 …

看电影相关的日语,柯桥日语培训

1&#xff0e;映画を見たいです。 我想看电影。 2&#xff0e;どこの映画館&#xff08;えいがかん&#xff09;で上映&#xff08;上映&#xff09;しますか。 在哪里的影院上映&#xff1f; 3&#xff0e;どこで映画が見られますか。 哪里能看电影呢&#xff1f; 4&#x…

稳定扩散AI 纹理生成器

推荐基于稳定扩散(stable diffusion) AI 模型开发的自动纹理工具&#xff1a; DreamTexture.js自动纹理化开发包 - NSDT 什么是稳定扩散&#xff1f; 从技术上讲&#xff0c;Stable Diffusion 是一种用于机器学习的潜在扩散模型 &#xff08;LDM&#xff09;。这种类型的专用深…

【dbeaver】添加mysql高低版本选择驱动

添加mysql高低版本选择驱动 连接到数据库->全部->查询mysql MySQL 版本驱动 8.0 MySQL 5 版本驱动 5.7.x 其他需要就&#xff1a;https://downloads.mysql.com/archives/c-j/ 密码查看 项目设置密码&#xff1a; File -> Project security ->设置密码 It i…

解决项目开发过程需要多个if else的情况

项目开发过程中虽然不建议使用过多的if-else-else if&#xff0c;但是总有一些情况是避免不了&#xff0c;举一个例子&#xff1a; struct DataBase {std::string type_; };void Update(DataBase _data) {if (_data.type_ "UiSCLKey"){}else if (_data.type_ &quo…

Ubuntu 22.04 安装水星无线 USB 网卡

我的 USB 网卡是水星 Mercury 的&#xff0c; 在 Ubuntu 22.04 下面没有自动识别。 没有无线网卡的时候只能用有线接到路由器上&#xff0c;非常不方便。 寻思着把无线网卡驱动装好。折腾了几个小时装好了驱动。 1.检查网卡类型 & 安装驱动 使用 lsusb 看到的不一定是准确…

法治智能起航 | 拓世法宝AI智慧政务一体机重塑法治格局,开启智能司法新篇章

在科技的巨轮推动下&#xff0c;我们的社会正快速迈向一个以数据和智能为核心的新时代。在这个波澜壮阔的变革中&#xff0c;人工智能&#xff08;AI&#xff09;显得尤为突出&#xff0c;它不仅是科技进步的象征&#xff0c;更是未来发展的助力者。 2023年&#xff0c;最高人…

linux 3.13版本nvme驱动阅读记录四

这里记录下在nvme_probe函数调用misc_register函数的总结。 static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) {//... create_cdev://利用miscdev结构体提供一些字符设备的操作(回调函数)&#xff0c;用户空间可以下发一些nvme的命令等scnprintf…

医学影像系统源码(MRI、CT三维重建)

一、MRI概述 核磁共振成像&#xff08;英语&#xff1a;Nuclear Magnetic Resonance Imaging&#xff0c;简称NMRI&#xff09;&#xff0c;又称自旋成像&#xff08;英语&#xff1a;spin imaging&#xff09;&#xff0c;也称磁共振成像&#xff08;Magnetic Resonance Imag…

Labview利用声卡捕获波形

一般的计算机上自带的声卡&#xff0c;均既有A/D功能&#xff0c;又有D/A功能&#xff0c;就是一款具备基本配置的数据采集卡&#xff0c;并且技术成熟&#xff0c;性能稳定。 后台如下&#xff1a;

【Word自定义配置,超简单,图文并茂】自定义Word中的默认配置,比如标题大小与颜色(参考科研作图配色),正文字体等

▚ 01 自定义样式Styles中的默认标题模板 &#x1f4e2;自定义标题的显示效果&#xff0c;如下图所示&#xff1a; 1.1 自定义标题的模板Normal.dotm 1.1.1 选择所需修改的标题 新建一个空白Word文档&#xff0c;依次选择菜单栏的开始Home&#xff0c;样式Styles&#xff0c;…

低代码信创开发核心技术(三):MDA模型驱动架构及元数据系统设计

前言 写最后一篇文章的时候&#xff0c;我本人其实犹豫了半年&#xff0c;在想是否发布出这篇文章&#xff0c;因为可能会动了很多人的利益。所以这篇文章既是整个低代码信创开发的高度总结&#xff0c;也是最为精华的一部分&#xff0c;它点明了低代码中最为核心的技术。虽然…

光刻掩膜版怎么制作的?

光掩膜版基本上是 IC 设计的“主模板”。掩模版有不同的尺寸。常见尺寸为 6 x 6 英寸一般的掩膜版由石英或玻璃基板组成。光掩膜版涂有不透明薄膜。更复杂的掩模版使用其他材料。 一般来说&#xff0c;术语“photo mask”用于描述与 1X 步进机或光刻系统一起使用的“主模板”。…