.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-MatchSQL 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