.NET面试题

.NET 基础

简述常用的委托及区别

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

Func 委托 有参有返回值 最多16个参数

Action 委托 有参无返回值 最多16个参数

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

事件与为委托的区别

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

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

abstract 和 interface 的区别、

abstract:抽象类

intetface:接口

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

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

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

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<T> 类来实现延迟初始化。

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

    private Singleton() { }

    public static Singleton Instance => 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;
    }
    }
    }
    }
  3. 饿汉式

    在类加载的时候就创建实例,保证在任何线程访问之前都已经存在实例。这可能会导致性能问题,因为即使不需要实例,也会被创建出来。

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

    private Singleton() { }

    public static Singleton Instance => instance;
    }
  4. 双重检查锁定

    这是一种优化过的懒汉式,使用了双重检查来减少加锁的频率,提高性能。

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

    private Singleton() { }

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

工厂模式

  1. 简单工厂模式(Simple Factory Pattern):

    这是工厂模式的最简单形式,由一个工厂类负责创建多个产品类的实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class SimpleFactory
    {
    public IProduct CreateProduct(string type)
    {
    switch (type)
    {
    case "A":
    return new ProductA();
    case "B":
    return new ProductB();
    default:
    throw new ArgumentException("Invalid product type");
    }
    }
    }

    public interface IProduct { }
    public class ProductA : IProduct { }
    public class ProductB : IProduct { }
  2. 工厂方法模式(Factory Method Pattern):

    工厂方法模式将每个具体产品的创建逻辑都封装在对应的工厂类中,每个产品都对应一个工厂类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public interface IFactory
    {
    IProduct CreateProduct();
    }

    public class ConcreteFactoryA : IFactory
    {
    public IProduct CreateProduct()
    {
    return new ProductA();
    }
    }

    public class ConcreteFactoryB : IFactory
    {
    public IProduct CreateProduct()
    {
    return new ProductB();
    }
    }

    public interface IProduct { }
    public class ProductA : IProduct { }
    public class ProductB : IProduct { }
  3. 抽象工厂模式(Abstract Factory Pattern):

    抽象工厂模式用于创建一系列相关或依赖的对象,将产品的创建逻辑抽象成一个接口,然后具体工厂实现这个接口来创建一组产品。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    csharpCopy code
    public interface IAbstractFactory
    {
    IProductA CreateProductA();
    IProductB CreateProductB();
    }

    public class ConcreteFactory1 : IAbstractFactory
    {
    public IProductA CreateProductA()
    {
    return new ProductA1();
    }

    public IProductB CreateProductB()
    {
    return new ProductB1();
    }
    }

    public class ConcreteFactory2 : IAbstractFactory
    {
    public IProductA CreateProductA()
    {
    return new ProductA2();
    }

    public IProductB CreateProductB()
    {
    return new ProductB2();
    }
    }

    public interface IProductA { }
    public class ProductA1 : IProductA { }
    public class ProductA2 : IProductA { }

    public interface IProductB { }
    public class ProductB1 : IProductB { }
    public class ProductB2 : IProductB { }

这些是工厂模式在 C# 中的几种常见实现方式。每种方式都有不同的适用场景,可以根据项目的需求和复杂度来选择合适的工厂模式实现。

简述异步与多线程的区别

异步编程

  1. 执行流程控制:异步编程旨在改善程序的执行流程控制,以便在等待耗时的操作(如 I/O 操作)完成时,能够释放主线程,不会阻塞其它任务的执行。
  2. 单线程:异步编程在 .NET 中通常基于单线程的事件循环模型。异步操作可能会创建新的线程这个取决于线程池是否会给当前异步分配新的线程,而是通过异步等待(如 async/await)来挂起执行,并在操作完成后返回主线程。
  3. 适用场景:适用于需要等待 I/O 操作、网络请求等耗时操作完成的情况,以避免阻塞主线程,提高应用的响应性。

多线程编程

  1. 执行流程控制:多线程编程旨在实现真正的并行执行,可以在多个线程上同时执行不同的任务。
  2. 多线程:多线程编程创建多个线程来并发执行任务,每个线程有自己的执行流程,可以在不同的核心上并行执行。
  3. 线程管理:多线程编程需要考虑线程的创建、销毁、同步、互斥等问题,涉及到了更多的线程管理细节。
  4. 并发性与并行性:多线程编程可以实现并行性,即在多个线程上同时执行任务。但要注意,多线程也可以导致竞态条件、死锁等问题,需要合适的同步机制。
  5. 适用场景:适用于需要高性能计算和处理的情况,例如多核处理器上的并行计算任务。

在 .NET 中,可以使用以下技术来实现异步编程和多线程编程:

  • 异步编程:使用 asyncawait 关键字来定义异步方法,并利用异步等待来实现非阻塞的操作。
  • 多线程编程:使用 Thread 类、Task 类、ThreadPool 等来创建和管理线程,并利用线程同步机制(如 MonitorMutexSemaphore 等)来避免竞态条件等问题。

为什么使用异步

异步编程旨在改善程序的执行流程控制,以便在等待耗时的操作(如 I/O 操作)完成时,能够释放主线程,不会阻塞其它任务的执行。使用异步能够充分使用计算机的cpu资源,提高运行性能并且异步操作的线程是由线程池操作的,在使用异步的时候我们通常不用在意线程的创建、销毁、同步、互斥等问题,同时还能充分的使用线程创建的线程(一个异步操作可能会创建一个新的线程 )。

简述DDD

领域驱动设计(Domain-Driven Design,简称 DDD)是一种软件设计方法论,强调在软件开发中将领域(Domain)作为核心,以解决复杂业务逻辑和领域模型的建立为重点。DDD 关注于将业务需求和软件设计紧密联系起来,以实现更清晰、可维护、可扩展的软件系统。

以下是 DDD 的一些关键概念和原则:

  1. 领域(Domain):领域是指软件系统所解决的业务问题领域,包括业务规则、流程、概念等。
  2. 领域模型(Domain Model):领域模型是将业务领域的核心概念、规则和流程抽象成软件对象的模型。它在 DDD 中扮演着核心角色,帮助开发者更好地理解和表达业务逻辑。
  3. 战术设计(Tactical Design):战术设计涉及如何将领域模型映射到实际的代码实现。这包括实体(Entities)、值对象(Value Objects)、聚合(Aggregates)、仓储(Repositories)等设计模式的应用。
  4. 战略设计(Strategic Design):战略设计关注整体系统架构,如如何组织领域模型、模块划分、领域服务、上下文映射等。
  5. 限界上下文(Bounded Context):领域驱动设计中,不同的部分可能使用不同的术语、规则和模型,为了解决不同领域模型之间的冲突,将领域模型划分为不同的限界上下文。
  6. 聚合(Aggregate):聚合是一组关联对象的根,用于保证对象的一致性和完整性。聚合内部的对象之间可以被外部对象访问,但外部对象不能直接访问聚合内部的对象。
  7. 领域事件(Domain Events):领域事件是表示领域中发生的事情的对象。它可以用于在不同的限界上下文之间传递信息,触发其他操作。

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

看我这篇文章就够了

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

简述常用的信号量

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

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

简述常用的锁以及区别

  1. 互斥锁(Mutex)
    • 互斥锁是一种最基本的锁,它用于确保在同一时间只有一个线程能够进入临界区。
    • 互斥锁可以被单独的线程锁定和解锁,这意味着只有锁定了互斥锁的线程可以访问临界区。
    • 只能由锁定线程释放,不适合在同一线程中多次加锁。
  2. 信号量(Semaphore)
    • 信号量是一种允许多个线程同时访问共享资源的锁,但是可以限制并发访问的线程数量。
    • 信号量维护一个可用许可数量,线程可以通过获取许可来进入临界区,访问完后释放许可。
    • 适用于限制同时访问某个资源的线程数量,例如线程池中的线程数控制。
  3. 读写锁(Read-Write Lock)
    • 读写锁允许多个线程同时读取共享资源,但只允许一个线程写入资源。
    • 当没有写入线程时,多个读取线程可以同时持有读锁。
    • 适用于读多写少的场景,可以提高并发性能。
  4. 自旋锁(Spin Lock)
    • 自旋锁是一种忙等待的锁,它会一直尝试获取锁,直到成功为止,不会让线程进入睡眠状态。
    • 自旋锁适用于临界区非常短小、线程等待时间很短的情况,避免线程切换带来的开销。

RabbitMq

看马先生这篇文章就够了

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