.NET面试题

.NET 基础

简述常用的委托及区别

Delegate:委托声明关键字,有返回值

Func:有参有返回值的委托,最多支持16个参数

Action:有参无返回值的委托,最多支持16个参数

委托可以作为参数在方法中传递

事件与委托的区别

委托:是一种数据类型,用于持有对一个或多个方法的引用。通过委托,你可以将方法作为参数传递给其他方法,实现回调机制和方法的动态调用

事件:是委托的一种特殊应用,用于实现发布-订阅模型。使用event关键字声明事件并指定事件委托的类型。事件允许对象通知其他对象在特定情况下执行操作,实现松耦合的通信机制

abstract 和 interface 的区别

abstract:抽象类

interface:接口

抽象类与接口的相同点是都可以被继承

抽象类的抽象方法与接口的方法只能声明不能有具体的实现,继承的子类必须实现抽象方法和虚方法

抽象类的虚方法子类可以继承,且子类可以重写抽象类的虚方法

const 与 readonly 都可以声明常量

readonly 与 const 关键字的区别

const:通常用于已知的常量,在编译时确定值,必须在声明时赋值

readonly:通常用于运行时被访问的值,可以在声明时赋值,也可以在运行时赋值(在构造函数中初始化)

Linq 原理

LINQ(Language Integrated Query)是一种在编程语言中集成查询功能的技术,它允许开发者通过类似于 SQL 的查询语法来查询和操作各种数据源,如集合、数据库、XML 等。LINQ 的核心原理涉及以下几个主要组件:

  1. 表达式树(Expression Tree): LINQ 查询被表示为表达式树。表达式树是一种数据结构,它以树形的方式表示代码逻辑。在 LINQ 中,查询操作和条件会被转换为表达式树,这使得查询的逻辑可以在运行时进行解析和操作。
  2. 查询提供程序(Query Providers): 每个数据源(如 LINQ to SQL、LINQ to Entities)都有相应的查询提供程序,用于将表达式树转换为底层查询语言(如 SQL)以实际执行查询。查询提供程序充当了 LINQ 查询与实际数据源之间的桥梁。
  3. 扩展方法(Extension Methods): LINQ 查询操作通常是通过扩展方法实现的。扩展方法是一种特殊的静态方法,它可以为现有类型添加新的方法。这些方法的名称通常与查询操作(如 Where、Select、OrderBy 等)相对应。
  4. 延迟执行(Deferred Execution): LINQ 查询在默认情况下是延迟执行的。这意味着查询不会立即执行,而是在实际需要结果时才会执行。这可以提高性能,因为只有在需要时才会从数据源中获取数据。
  5. 匿名类型(Anonymous Types): LINQ 查询可以返回匿名类型的集合,这使得在查询结果中返回特定字段的子集成为可能。
  6. 查询语法和方法语法: LINQ 提供了两种主要的查询语法:查询表达式和方法链。查询表达式使用类似于 SQL 的语法来编写查询,而方法链使用一系列扩展方法来编写查询。两种语法最终都会被翻译成表达式树。
  7. 查询优化: 查询提供程序负责将表达式树转换为实际的查询语言。这包括将查询进行优化,以提高查询性能。

简述.net core 生命周期的区别

在ASP.NET Core中,依赖注入(DI)是一种设计模式,用于将组件的依赖关系从它们的实现中解耦,从而提高代码的可维护性和可测试性。ASP.NET Core通过内置的依赖注入容器来管理和解析服务的生命周期。以下是ASP.NET Core中支持的三种主要服务生命周期:

  1. 瞬时(Transient)生命周期:
    • 对于瞬时生命周期的服务,每次调用**IServiceProvider.GetService<T>**都会创建一个新的实例。
    • 这种生命周期适用于轻量级、无状态的服务,每次调用需要获得一个全新的实例。
    • 在依赖注入容器中,可以使用**services.AddTransient<TService, TImplementation>()**方法注册瞬时生命周期的服务。
  2. 作用域(Scoped)生命周期:
    • 对于作用域生命周期的服务,容器会为每个HTTP请求创建一个单独的实例,并在整个请求周期内重用这个实例。
    • 这种生命周期适用于需要在一个HTTP请求范围内共享状态的服务,例如数据库上下文。
    • 在依赖注入容器中,可以使用**services.AddScoped<TService, TImplementation>()**方法注册作用域生命周期的服务。
  3. 单例(Singleton)生命周期:
    • 对于单例生命周期的服务,容器会创建一个单一的实例,并在整个应用程序的生命周期内重用这个实例。
    • 这种生命周期适用于全局唯一的、可共享的状态,例如配置信息、日志记录器等。
    • 在依赖注入容器中,可以使用**services.AddSingleton<TService, TImplementation>()**方法注册单例生命周期的服务。

ASP.NET Core的依赖注入容器负责创建、管理和释放这些不同生命周期的服务,确保按需创建、重用和销毁实例,以满足应用程序的需求。你可以根据每个服务的特性和需求来选择适当的生命周期。

如何设计一个高并发接口(10k面试都被问到了)

  1. 分布式架构: 采用分布式架构,将系统拆分为多个服务,每个服务负责特定的功能。这有助于将负载分散到多个服务器上,提高系统的扩展性和容错性。
  2. 负载均衡: 使用负载均衡器将请求均匀分发给多个服务器,防止单个服务器过载。常见的负载均衡算法包括轮询、最少连接等。
  3. 数据库优化: 使用数据库连接池、索引、分区、缓存等手段来优化数据库性能。避免频繁的数据库查询,可以采用缓存技术来减轻数据库的压力。
  4. 缓存策略: 使用缓存来存储热门数据,减少对数据库的访问。选择适当的缓存策略,如内存缓存、分布式缓存(如 Redis)等。
  5. 异步处理: 对于耗时的操作,可以采用异步处理来释放请求线程,提高系统的并发能力。使用异步编程模型,如 async/await。
  6. 限流和熔断: 实现请求的限流和熔断机制,防止因为高并发而导致系统崩溃。可以使用开源工具,如 Hystrix。
  7. 优化数据库事务: 缩短数据库事务的时间,避免长时间持有数据库锁。考虑将事务拆分成更小的操作,减少锁的竞争。
  8. 无状态设计: 尽量设计无状态的接口,将状态保存在客户端或共享缓存中,这有助于水平扩展和负载均衡。
  9. 资源池: 使用资源池来管理资源,如数据库连接、线程等。资源池可以有效地分配和复用资源,减少资源的创建和销毁开销。
  10. 监控和调优: 使用监控工具来实时监测系统性能,并根据监控数据进行调优。及时发现性能瓶颈并进行优化。
  11. 并发测试: 在开发阶段进行并发测试,模拟多用户同时访问系统,发现并解决潜在的并发问题。
  12. 水平扩展: 使用水平扩展来增加服务器数量,从而提高系统的并发能力。可以采用容器技术,如 Docker,来快速扩展。

讲述Grpc 和 Http 服务调用的区别

1. 通信协议:

  • gRPC:基于HTTP/2协议的开源RPC框架,使用二进制流进行数据传输。HTTP/2的多路复用和头部压缩特性使得 gRPC 在网络性能方面具有优势。
  • HTTP:传统的HTTP协议,通常使用文本格式传输数据,HTTP/1.1和HTTP/2都被广泛使用。

2. 数据格式:

  • gRPC:支持多种序列化格式,如Protocol Buffers(protobuf)、JSON等。Protocol Buffers 是 gRPC 默认的序列化格式,其二进制编码使得数据更小、传输更快。
  • HTTP:通常使用JSON、XML等文本格式进行数据传输,相对于二进制格式,数据量较大,传输速度较慢。

3. 接口定义:

  • gRPC:使用Protocol Buffers语言定义服务和消息的接口,然后通过gRPC工具生成对应的客户端和服务器端代码。
  • HTTP:通常使用REST(Representational State Transfer)风格定义接口,使用HTTP动词(GET、POST、PUT、DELETE等)进行操作。

4. 序列化与反序列化:

  • gRPC:使用高效的二进制序列化和反序列化,效率较高。
  • HTTP:使用文本格式进行序列化和反序列化,效率相对较低。

5. 性能:

  • gRPC:由于基于HTTP/2和二进制传输,具有较低的延迟和更高的吞吐量,适用于高性能的场景。
  • HTTP:性能较 gRPC 差一些,但适用于各种类型的应用。

6. 安全性:

  • gRPC:支持TLS加密,可以保障通信的安全性。
  • HTTP:也支持TLS加密,确保数据在传输过程中的安全。

7. 支持的语言:

  • gRPC:提供多种编程语言的支持,包括C++、Java、Python、Go等。
  • HTTP:几乎所有编程语言都支持对HTTP的调用。

8. 适用场景:

  • gRPC:适用于高性能、大规模并发、低延迟的场景,如微服务架构中的服务间通信。
  • HTTP:适用于广泛的Web应用程序、浏览器和服务器之间的通信。

ElstaticSearch基本语法以及.NetCore中的使用

死锁的原因、如何解决死锁

死锁是多线程或并发程序中的一种常见问题,它发生在两个或多个线程相互等待对方释放资源,从而导致所有线程都无法继续执行的情况。死锁通常涉及以下四个必要条件:

  1. 互斥条件(Mutual Exclusion): 指资源只能同时被一个线程占用,即资源不能被多个线程同时访问。
  2. 请求与保持条件(Hold and Wait): 指线程在持有一些资源的同时还在请求其他资源,而且不会释放已持有的资源。
  3. 不可剥夺条件(No Preemption): 指已被一个线程获得的资源不能被其他线程强制性地剥夺,只能由持有者线程释放。
  4. 循环等待条件(Circular Wait): 指多个线程之间形成了一种循环等待资源的关系,每个线程都在等待下一个线程释放资源。

为了解决死锁问题,可以考虑以下方法:

  1. 破坏死锁必要条件: 针对四个死锁必要条件,可以采取措施来破坏其中一个或多个条件。例如,通过引入超时机制(不可剥夺条件),限制线程持有资源的数量(循环等待条件)等。
  2. 使用资源分级策略: 将资源进行分类和分级,确保线程只能按照一定的顺序申请资源,从而避免循环等待条件。
  3. 使用事务和锁定顺序: 在获取多个资源时,始终按照相同的顺序获取,这有助于避免不同线程之间发生死锁。
  4. 使用超时和重试: 对于获取锁的操作,可以设置超时时间,如果超过一定时间仍无法获取锁,就放弃或重新尝试。
  5. 死锁检测和恢复: 在一些情况下,可以使用算法检测死锁的发生,一旦检测到,可以采取一些策略来中断其中一个或多个线程,以解除死锁。
  6. 资源剥夺和回退: 当检测到死锁时,可以中断一些线程并释放其占用的资源,然后重新开始这些线程。
  7. 合理设计并发: 设计良好的并发架构,避免不必要的资源占用,合理规划资源的使用,可以减少死锁的发生。

讲述工作中常用的设计模式及实现

单例模式(单例的实现方式)

单例的实现方式一般是使用lock锁、私有化无参构造函数

  1. 经典懒汉式(Lazy Initialization):这是最常见的单例模式实现,它在需要的时候才创建实例。可以使用 Lazy&lt;T&gt; 类来实现延迟初始化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    csharpCopy code
    public class Singleton
    {
    private static readonly Lazy&lt;Singleton&gt; instance = new Lazy&lt;Singleton&gt;(() =&gt; new Singleton());

    private Singleton() { }

    public static Singleton Instance =&gt; instance.Value;
    }
  2. 线程安全懒汉式:这是懒汉式的线程安全版本,通过加锁来确保在多线程环境下只有一个实例被创建。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    csharpCopy code
    public class Singleton
    {
    private static Singleton instance;
    private static readonly object lockObject = new object();

    private Singleton() { }

    public static Singleton Instance
    {
    get
    {
    lock (lockObject)
    {
    if (instance == null)
    {
    instance = new Singleton();
    }
    return instance;
    }
    }
    }
    }

饿汉式:在类加载的时候就创建实例,保证在任何

乐观锁 vs 悲观锁

核心对比表

对比维度 乐观锁(Optimistic Locking) 悲观锁(Pessimistic Locking)
核心思想 假设冲突发生概率低,只在提交时检查冲突 假设冲突频繁发生,提前加锁防止并发修改
实现方式 版本号(Version)、时间戳(Timestamp)、CAS(Compare and Set) 数据库锁(行锁、表锁)、SELECT ... FOR UPDATE
适用场景 读多写少(如电商库存、文档协作编辑) 写多读少(如银行转账、高并发库存扣减)
性能开销 冲突少时性能高,冲突多时可能频繁回滚 锁竞争可能导致阻塞,影响并发性能
一致性保证 最终一致性(提交时检测冲突) 强一致性(通过锁直接阻塞并发操作)
典型实现 EF Core 的 ConcurrencyCheck、HTTP 的 If-Match SQL Server 的 UPDLOCK、MySQL 的 FOR UPDATE

1. 乐观锁(Optimistic Locking)

工作原理

  1. 读取数据时获取版本号(如 Version=1)。
  2. 修改数据前检查版本号是否变化:
    • 若版本号一致,提交修改并更新版本号(如 Version=2)。
    • 若版本号不一致,抛出异常或重试。

代码示例(EF Core)

1
2
3
4
5
6
7
8
9
10
public class Product
{ public int Id { get; set; }
public string Name { get; set; }
[ConcurrencyCheck] // 标记为并发令牌
public int Version { get; set; }}
// 修改数据时自动检查版本号
var product = context.Products.Find(1);
product.Name = "New Name";
context.SaveChanges(); // 若版本号冲突,抛出 DbUpdateConcurrencyException
}

简述认证授权流程(工作中常用的认证授权策略)

看我这篇文章就够了

.net core 自定义授权策略提供程序进行权限验证 - 布吉岛1c - 博客园 (cnblogs.com)

简述常用的信号量

看我这篇文章就够了(文章涉及的demo已更新)

关于c#多线程中的几个信号量 - 布吉岛1c - 博客园 (cnblogs.com)

RabbitMq

看马先生这篇文章就够了

https://www.cnblogs.com/fantasy-ke/p/17555153.html