1、谈谈你对协变和逆变的理解

协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。

协变(out):适用于返回值,允许派生类型赋值给基类型。

逆变(in):适用于方法参数,允许基类型赋值给派生类型。

它们主要用于接口和委托,提供了更灵活的泛型类型兼容性。

static void Main(string[] args)
{
    //协变委托 Func<out T> 只能作为返回值,子类返回给父类
    Func<string> func = () => "";
    Func<object> func1 = func;
    //协变接口 IEnumerable<out T> 只能作为返回值,子类返回给父类
    IEnumerable<string> covariant = new List<string>();
    IEnumerable<object> covariant1 = covariant;
    //协变数组
    string[] strings = new string[10];
    object[] objects = strings;
    //逆变委托 T只能作为参数,父类赋值给子类
    Action<object> action = (s) => { };
    Action<string> action1 = action;
    action1("");
    //逆变接口 T只能作为参数,父类赋值给子类
    IComparable<object> contravariant = default;
    IComparable<string> contravariant1 = contravariant;
    contravariant1.CompareTo("");
}

2、谈谈你对委托和事件的理解

委托 (Delegate) :委托是一种特殊的类型,表示对方法的引用,可以把委托是一个类型安全的函数指针,可以指向具有特定签名和返回类型的方法。多播委托注意异常处理,委托链上的一个方法异常,后续的方法将不会执行。

多播委托 (Multicast Delegate) :多播委托是指一个委托可以同时指向多个方法,并按顺序调用这些方法。

事件 (Event):事件是基于委托的高级封装,提供了一种发布-订阅模式,用于在对象之间发布消息和订阅消息。事件通常用于通知订阅者某些操作已经发生。EventHandler(object sender,EventArgs arg)就是GUI开发中最经典的事件模型。用于通知某些事件发生。

3、in、out、ref三个引用传递的关键字有什么区别

名称作用使用场景是否需要提前初始化
in只读参数,按引用传递高效传递大对象但不希望修改其值不需要
ref输出参数,按引用传递方法内部可以读取和修改参数值,调用前必须初始化需要
out输出参数,按引用传递方法内部必须赋值,调用前不需要初始化 | 从方法返回多个值不需要

4、装箱和拆箱

装箱:值类型转换为引用类型。

拆箱:引用类型转换为值类型

装箱和拆箱会有性能上的损失,应尽量避免使用。

5、C#中哪些类型是值类型,哪些类型是引用类型

值类型:数值类型(int、double、float)、字符char、布尔类型Bool、结构体Struct。

引用类型:类、接口、委托、数组、数据相关结构类(List、LinkNodeList、Dictionary、Stack、Queue、Set、HashSet、SortedSet、SortedDictionary)

5、值类型和引用类型的区别

值类型的父类是ValueType,引用类型的父类是Object。ValueType也继承自Object。

值类型通常分配在栈上,引用类型通常分配在堆上。

5、简单介绍下List<Int>的内存分配

List<T> 是一个动态数组,内部使用T[]维护。

1、List<T> 的实例对象分配在堆上。

2、内部数组对象分配在堆上。

6、C#如何实现线程同步

互斥体(Mutex)、信号量(限制访问线程数量)、事件(手动:所有被唤醒,自动:一个被唤醒)、定时器、进程、线程
原子操作函数(Interlocked)、关键段(lock)、临界区(lock)、条件变量、旋转锁std::atomic_flag(SpinLock)、读写锁AcquireSRWLockExclusive

Task线程同步等待。

7、单例模式有哪几种写法,各有什么优缺点

饿汉式:简单粗暴, 懒汉式:双重加锁.Lazy<T>泛型类懒加载

8、C#垃圾回收的机制是什么

分代回收:0、1、2、大对象堆(>85kb) 标记-清楚-压缩

自动触发:0代空间满了之后,触发一次,把0代中生命周期比较长的移动到1代中。

手动触发(GC.Collection)

对于大对象,一般建议使用对象池、数组池来管理内存。

9、如何减少垃圾回收器的压力

1、减少对象分配,可以使用对象池或者系统自带IOC容器维护对象。

2、对于需要频繁创建的对象,如果业务逻辑不是很复杂,可以使用结构体代替类,因为结构体分配在栈上,垃圾回收器不会回收栈上的内存。

3、减少装箱和拆箱,装箱和拆箱会在堆上创建临时对象,并且这些对象生命周期很短,大范围使用装箱和拆箱会给GC造成较大压力。

4、使用数组池:ArrayPool<T>

高效的数组对象池。主要作用是减少频繁分配和释放数组所带来的性能开销,尤其是在需要频繁使用短生命周期数组的场景中。

减少垃圾回收器GC的压力。

10、对耦合和解耦有什么理解吗

耦合:指的是两个或多个模块之间的直接依赖程度。耦合度越高,模块之间的依赖越紧密,修改一个模块可能会影响到其他模块。

解耦:解耦是指通过减少模块之间的直接依赖,使得模块更加独立。解耦的目标是提高代码的可维护性、可扩展性和可测试性。

如何解耦:

(1)单一职责原则:一个类尽量承担一类功能,避免在一个类中编写大量的功能模块。

(2)依赖注入:.NET框架提供了强大的依赖注入功能,可以避免在各个模块之间直接相互引用,通过注入的方式,可以减少模块间的依赖程度。(使用最广泛,适用于绝大多数场景)

(3)事件驱动:通过事件、消息队列、弱引用的方式来减少模块之间的直接调用,(通过事件和委托、弱引用、强引用等适用于发布订阅的场景)

(4)接口和抽象:面向接口编程,尽量少面向实例编程。当一个功能有多个实现的时候,就可以使用接口封装功能,比如一个打印机需要调用多个驱动程序打印,一个下载功能需要普通下载和断点下载,就可以接口提供抽象能力(适用于同一个功能有多个实现的场景)

(5)、通过设计模式:工厂模式、中介者模式实现解耦。

10、控制反转和依赖注入

控制反转:控制反转是一种设计原则,指将对象的创建和依赖关系的管理从对象本身转移到外部容器或框架中(工厂容器)。

通过这种方式,模块之间的依赖关系被反转,模块不再直接控制其依赖项,而是由外部容器来管理。模块对被依赖对象的创建交给了外部容器,而不是模块本身,这个就是控制权的转移。

依赖注入:依赖注入是控制反转的具体实现方式,可以通过构造函数、属性、方法等方式将对象注解注入一个模块。

对ABP框架有没有什么了解

对应用程序进行模块划分:逻辑层、服务层、DTO层、接口层。

依赖注入、日志记录、权限认证。

Task 会开出一个线程吗? Task 是什么东西 ? 是否了解 async await

Task 不一定会创建线程。它的执行由 TaskScheduler 决定,默认情况下使用线程池。当线程池线中没有空闲线程的时候,可能会创建新的线程来执行任务。

对于CPU密集型任何,一般会使用线程池中的线程。对于IO密集型任务,比如文件读写、网络操作通常会使用操作系统的I/O异步机制来完成任务。async/await不一定会有线程,如果方法本身没有await关键字或者调用Task.Delay都不会用新线程执行任务。

对Task的ConfigurationAwaier方法是否有理解,这个方法的主要作用是什么

主要用于设置是否捕获上下文,主要用于UI线程中多线程执行。捕获上下文有性能开销,默认捕获,可以直接在线程池中继续执行的,可以直接不用捕获同步上下文。

线程同步上下文的多个实现

1、手动捕获线程的同步上下文(SynchronizationContext).Post

2、Task.ConfigurationAwaier配置是否捕获上下文。

3、任务调度器:TaskScheduler。通过当前线程的任务调度器直接在UI线程中执行。

3、利用winform/wpf自带的同步上下文封装类执行代码。BeginInvoke

引用对象如何进行深拷贝和浅拷贝

浅拷贝:MemberwiseClone 深拷贝:手动拷贝,一个个赋值/序列化反序列化/AutoMappe三方框架

Const和Static关键字有什么区别

const:编译时常量,编译阶段就已经确定,不会占用堆栈内存。

static:运行时变量,可以更改,占用堆栈内存。

值类型和引用类型的生命周期有什么区别

值类型:不依赖垃圾回收器,出了作用域之后会被清掉。

引用类型:依赖垃圾回收器的机制进行回收。

DI的生命周期 是什么, autoface 的生命周期

瞬时生命周期、作用域生命周期(数据库上下文、用户会话、Http请求上下文)、单例生命周期

瞬时生命周期优点:解耦,通过依赖注入注入接口,而不需要关注具体实现。

能否简单介绍一下线程池

多线程编程方式

Thread、ThreadPool、Task、Parallel类

异步的实现方法

async/await关键字、TPL(Task并行库)、APM(异步委托+回调函数+BeginInvoke)

10、如何在C#中检测内存泄露

(1)Visual Studio 诊断工具

(2)专业工具:ANTS Memory Profiler、JetBrains dotMemory、PerfView (微软免费工具)

(3)句柄泄露:句柄对象(文件句柄、进程句柄、线程句柄、注册表句柄)、网络连接、COM组件句柄、线程同步对象、GDI+对象、窗体句柄。

C#中什么场景会导致内存泄漏,如何排查内存泄露问题。

(1)非托管资源未释放,实现IDispose接口的对象都需要手动调用Dispose方法或者使用using关键字确保非托管资源可以释放。

(2)事件未注销:订阅者必要的时候,没有通过-取消订阅,那么发布者会一直拥有对订阅者的引用,导致订阅者无法被GC回收。

(3)全局静态对象:静态对象的生命周期伴随整个应用程序的生命周期,有些全局的集合对象存放了一些无用的对象,会导致GC无法对对象进行回收。

(4)IDispose对象忘记释放、多次释放、释放顺序错误、多线程环境下释放错误都有可能导致内存泄漏。

(5)循环引用:类设计不合理,A中维护B,B中维护A。会导致GC永远无法回收这两个对象,因为这两个对象形成一种死锁。

能够自己实现一个分布式锁

进程间通信方式

管道、共享内存、消息队列、套接字、数据库、文件、注册表

Dispose一般释放什么类型的资源

如果 Dispose 方法中释放了非托管资源(如文件句柄、网络连接、进程管理、线程管理、数据库连接等),这些资源会立即被释放。

C#弱引用和强引用的区别和优缺点

弱引用可以判断对象是否被GC回收,适合缓存场景。可以提高内存利用率,GC可以回收弱引用对象。

强引用:默认的引用方式,只要有一个引用指向对象,GC就不会对其进行回收。

C# 内存泄漏排查方法

内存管理单元MMU:虚拟地址和实际地址的映射,页表和页框
进程间通信:管道、共享内存、消息队列、套接字、数据库、文件、注册表、
序列化:效率高、速度快、内存小、带宽低、可读性差、兼容性差。
WMI系统管理、硬件管理、软件管理、监控报警

设计模式有哪些

创建型模式:单例模式、简单工厂、抽线工厂、原型模式、构建者模式。

结构型模式:适配器模式、代理模式、

行为型模式:状态模式、观察者模式、策略模式、命令模式、

WPF中直接路由事件、路由事件、冒泡事件、自定义路由事件

路由事件分为三种:直接、冒泡、隧道

1、通过e.Handled控制事件传播。

2、冒泡事件:从事件源开始,向上传播,直到根元素。适合在父控件中处理子空间的事件,可以通过e.Handled=true阻止事件传播。

3、隧道事件:事件从根元素开始,向下传播,直到事件源,通常以为Preview 开头。全局快捷键处理,拦截鼠标事件,控制子控件行为,全局鼠标点击,限制输入为数字。

4、自定义路由事件:

WPF触发器主要作用

主要用于对UI元素进行动态控制,当属性满足特定条件时,修改控件样式和行为。

类型:属性触发器、数据触发器、事件触发器、多触发器(多个属性或者属性组合起来一起触发)

WPF命令是如何工作的

Command主要用于将UI界面和业务逻辑绑定。允许业务逻辑重用。可以将一个方法绑定到多个控件。

工作流程:

1、命令源触发:用户点击

2、调用CanExecute:是否可以执行命令

3、调用Execute:执行命令

4、更新命令状态:是否可以执行。

WPF内置命令有哪些

ApplicationCommand命令:新建、保存、打开、打印、关闭

EditingCommand:复制、粘贴、删除、撤销

NavigationCommands:前进后退

ComponentCommands:上下左右命令

WPF路由策略有哪些

冒泡和隧道,冒泡从事件源头空间开始沿着逻辑树向上,直到根节点。隧道从根节点向下传播到时间源头控件。

WPF类层次结构

Object(基类)、Dispatcher(消息管理、线程同步)、DependObject(依赖对象)、Visual(视觉树)、UIElement(逻辑树)、FrameworkElement(高级样式和功能)、Button等子控件。

MVVM中的View、ViewModel、Model分别承担什么职责

View(xaml):视图页面,负责定义和现实UI。

ViewModel:负责连接View和Model,处理业务逻辑,实现数据绑定。

Model:封装UI逻辑和数据,和数据源交互。一般实现INotifyPropertyChanged,负责通知UI变化。

基本流程:用户点击View->VM处理业务→通知View更新数据。

WPF中的作用

负责UI线程同步、管理窗体消息循环、控制UI线程优先级。

WPF依赖属性和附加属性的区别

依赖属性:定义在中,只能在当前控件上进行绑定。

附加属性:定义在任何地方,一般可以绑定在任意控件的属性绑定。

WPF逻辑树和视觉树的区别

逻辑树:定义控件之间的逻辑关系,不管控件是显示还是隐藏都会在逻辑树中体现。用于数据绑定、资源查找和事件路由

视觉树:定义目前窗体中,可是元素的树状管理。主要是包括显示的控件。控件的所有视觉元素,包括模板和样式生成的子元素

视觉树是逻辑树的子集吗

不是的

?    逻辑树:
?    描述控件的逻辑结构,关注控件的内容和层次关系。
?    主要用于数据绑定、资源查找和事件路由。
?    不包含控件的视觉细节(如模板和样式生成的子元素)。
?    视觉树:
?    描述控件的视觉呈现,关注控件的渲染细节。
?    包含控件的所有视觉元素,包括模板和样式生成的子元素。
?    用于低级别的事件处理(如鼠标事件)和渲染。

WPF窗体生命周期

OnSourceInitialized    在创建窗体源时发生。
Loaded    当前窗体内部所有元素完成布局和呈现时引发此事件
OnActivated    当前窗体成为前台窗体时引发此事件。(表示鼠标聚焦在当前窗体中)
OnContentRendered    当窗体的内容首次呈现时调用,可以用于确保窗体已经完全加载并呈现。
OnDeactivated    当前窗体不再是前台窗体时发生。(表示鼠标不聚焦在当前窗体中)
OnClosing    窗体关闭前触发,可以在此事件中取消关闭操作。
OnClosed    窗体关闭后触发,可以在此事件中释放资源。

WPFApplcation生命周期

OnStartup    在应用程序启动时触发。用于初始化全局资源或进行其他启动设置。
OnActivated    当应用程序成为前台应用程序时发生。(表示鼠标聚焦在当前程序中)
OnDeactivated    当应用程序不再是前台应用程序时发生。(表示鼠标不聚焦在当前程序中)
OnSessionEnding    在用户通过注销或关闭操作系统而结束 Windows 会话时发生
OnExit    在应用程序关闭之前发生(表示退出应用程序时)

WPF支持MDI吗

WPF 不支持 MDI。 UserControl 可以提供与 MDI 相同的功能。

索引设计基本原则

1、逻辑主键:每个表必须有一个逻辑主键。聚集索引。

2、经常where查询的列建立索引

3、字符串索引:指定索引前缀长度,节省大量所以控件。

聚集索引和非聚集索引

SQL语句主要有哪些功能

1、增删改查CREATE、DROP、ALTER

2、Insert、Delete、Update、Select。

3、Grant、Revoke、Commit、RollBack

数据库锁有哪些

1、表级锁:对整个表加锁,限制表中所有行的操作。适合批量新增更新。并发低,开销小。

2、行级锁:对表中某行加锁,允许其他事务操作表中的其他行。并发高,开销大

3、页级锁:介于行和表之间的锁,性能和并发均衡,可能导致思索。

共享锁(多个事务同时读取资源,禁止修改资源 Select)、排他锁(只允许一个事务对行进行读写操作 Insert、Update、Delete)、意向锁(意向共享锁和意向排他锁)、更新锁(更新之前先加锁,防止死锁。更新时升级为排他锁Select…for update)

乐观锁、悲观锁。

如何防止数据库死锁

1、数据量和访问量不大的情况下,可以直接使用表级锁

2、避免在事务中执行耗时操作,尽量将业务逻辑在CPU中 计算好之后,交给数据库更新。

3、设置锁超时,超时之后,事务会执行失败。应用程序接收到事务执行失败之后,可以重新执行事务。

4、启用死锁检测,大多数数据库都有死锁检测,当自动识别有死锁之后,会随机终止死锁中的一个事务。

5、分析和优化SQL语句。

6、读取数据的时候,不用加锁。直接使用NOLock关键字。

7、数据库读写分离。读操作不用加锁,优化查询速度。

8、使用分布式锁,通过在外部控制多线程同时访问数据库资源。但此时要注意数据库并发可能会降低。

浏览器输入url到最终页面渲染会经历哪些流程 尽可能 详细的给我介绍

1、浏览器封装Http Get请求。

2、DNS解析:浏览器DNS缓存、操作系统DNS缓存、配置的DNS服务器查询。

3、建立TCP连接

4、如果是Https,还需要TLS/SSL认证。(

(4.1)、客户端发送clienthello:浏览器发送加密算法和协议版本

(4.2)、服务器返回serverhello:服务器返回数字证书公钥。

(4.3)、浏览器验证证书的合法性(通过CA的证书链)

(4.4)、浏览器生成会话密钥,并通过服务器的公钥加密后发送给服务器。

(4.5)、服务器确认会话密钥,双方使用对称加密通信。

5、浏览器通过发送Http报文对应的字节流到服务器(可能通过代理转发)

6、服务器接受请求,(可能通过Nginx反向代理)

7、浏览器接受服务器返回的响应字节流。

8、根据不同的响应码做对应的处理。2xx成功3xx需要跳转4xx错误。

9、开始渲染界面。

工业控制协议

modbus通讯协议:主从式异步半双工通信协可以使一个主站对应多个从站进行双向通信。它描述了一控制器请求访问其它设备的过程,如何回应来自其它设备的请求,以及怎样侦测错误并记录。它制定了消息域格局和内容的公共格式。 地址 + 功能码+ 数据 + 校验

RS-232通讯协议

RS-485通讯协议

应用层协议有哪些

Http协议、MQTT协议(发布订阅)、Modbus(工业控制协议)、SMTP邮件传输协议、DNS协议、Telnet远程登录协议、SSH远程登录协议、DHCP动态主机配置协议、WebSocket协议、RTP/RTCP音视频流协议

Modbus功能码有哪些

WPF绑定系统binding系统

Source:绑定源、Target:绑定的目标对象、BindingMode:绑定方向(TwoWay、OneWay、OnTime、OneWayToSource、Default)、UpdateSourceTrigger:数据更新、Path:绑定目标对象

Redis数据类型

字符串、列表、集合、哈希表、哈希set、地图空间

Redis单线程:关闭任务队列、AOF持久化、异步执行耗时命令

Redis持久化:AOF持久化:将每一条执行都写入日志。RDB:将某一时刻的数据持久化写道磁盘配置save和bgsave。混合持久化:

RDB:数据恢复速度快,效率高,丢失数据少。AOF:丢失数据少,数据恢复较慢

Redis主从复制:哨兵模式:监控redis节点状态,提供故障转移功能。

过期时间:过期字典查询。惰性删除+定期删除两种策略搭配使用。

缓存雪崩:短时间缓存失效,导致请求到服务器

缓存击穿:热点数据国过期。

缓存穿透:请求不存在的key

互斥体(Mutex)、信号量(限制访问线程数量)、事件(手动:所有被唤醒,自动:一个被唤醒)、定时器、进程、线程
原子操作函数(Interlocked)、关键段(lock)、临界区(lock)、条件变量、旋转锁std::atomic_flag(SpinLock)、读写锁AcquireSRWLockExclusive

进程间通信:管道、共享内存、消息队列、套接字、数据库、文件、注册表、
序列化:效率高、速度快、内存小、带宽低、可读性差、兼容性差。
WMI系统管理、硬件管理、软件管理、监控报警