本文介绍的最新高级C#面试知识点通常涵盖了语言本身的深入理解以及与.NET框架相关的高级主题。以下是一些高级C#面试知识点。
高级C#面试知识点
这些知识点涵盖了C#编程的广泛领域,根据您所面试的具体职位和公司需求,您可能需要更深入地了解其中的一些方面。在面试前,建议您复习这些知识点并准备好能够清晰解释和演示这些概念的示例。
一、多线程
在介绍多线程之前,简单的说一些概念,这些概念有利于你在Windows环境下编程更好的理解基础性的知识,当然,Linux环境下编译也是相通的。
1、进程:当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。 而一个进程又是由多个线程所组成的。
2、线程:线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
3、句柄:句柄是Windows系统中对象或实例的标识,这些对象包括模块、应用程序实例、窗口、控制、位图、GDI对象、资源、文件等。
4、多线程
(4.1)多线程概念
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
(4.2)多线程优点
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。(牺牲空间计算资源,来换取时间)
(4.3)多线程缺点
线程也是程序,所以线程运行需要占用计算机资源,线程越多占用资源也越多。(占内存多)
多线程需要协调和管理,所以需要CPU跟踪线程,消耗CPU资源。(占cpu多)
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。多线程存在资源共享问题)
线程太多会导致控制太复杂,最终可能造成很多Bug。(管理麻烦,容易产生bug)
(4.4)为什么计算机可以多线程
程序运行需要计算机资源,操作系统就会去申请CPU来处理,CPU在执行动作的时候是分片执行的。
分片:把CPU的处理能力进行切分,操作系统在调度的时候,按照切片去处理不同的计算需求,按照规则分配切片计算资源,对于同一个计算机核心来讲,所有的运行都是串行的,但是因为分片的存在,感觉几个程序同时在向前推进。
(5)何时建议使用多线程
当主线程试图执行冗长的操作,但系统会卡界面,体验非常不好,这时候可以开辟一个新线程,来处理这项冗长的工作。
当请求别的数据库服务器、业务服务器等,可以开辟一个新线程,让主线程继续干别的事。
利用多线程拆分复杂运算,提高计算速度。
(6)何时不建议使用多线程
当单线程能很好解决,就不要为了使用多线程而用多线程。
5、同步,异步
(5.1)同步方法
线性执行,从上往下依次执行,同步方法执行慢,消耗的计算机资源少。
(5.2)异步方法
线程和线程之间,不再线型执行,多个线程总的耗时少,执行快,消耗的计算机资源多,各线程执行是无序的。
6、C#中的多线程
Thread/ThreadPool/Task 都是C#语言在操作计算机的资源时封装的帮助类库。
Thread是.Net最早的多线程处理方式,它出现在.Net1.0时代,虽然现在已逐渐被微软所抛弃,微软强烈推荐使用Task,但从多线程完整性的角度上来说,我们有必要了解下早期多线程的是怎么处理的,以便体会.Net体系中多线程处理方式的进化。
- 后台线程,界面关闭,线程也就随之消失
- 前台线程,界面关闭,线程会等待执行完才结束
- 设置优先级只是提高了他被优先执行的概率
- 为了解决多线程竞用共享资源的问题,引入数据槽的概念,即将数据存放到线程的环境块中,使该数据只能单一线程访问。
- AllocateNamedDataSlot命名槽位和AllocateDataSlot未命名槽位,在主线程上设置槽位,使该数据只能被主线程读取,其它线程无法访问
- 利用特性[ThreadStatic],在主线程中给ThreadStatic特性标注的变量赋值,则只有主线程能访问该变量
- 利用ThreadLocal线程的本地存储,在主线程中声明ThreadLocal变量,并对其赋值,则只有主线程能访问该变量
.NET Framework2.0时代,出现了一个线程池ThreadPool,是一种池化思想,如果需要使用线程,就可以直接到线程池中去获取直接使用,如果使用完毕,在自动的回放到线程池去;
1、ThreadPool好处
不需要程序员对线程的数量管控,提高性能,防止滥用,去掉了很多在Thread中没有必要的Api
2、线程池如何分配一个线程
QueueUserWorkItem方法,将方法排入队列以便开启异步线程,它有两个重载。
QueueUserWorkItem(WaitCallback callBack),WaitCallback是一个有一个object类型参数且无返回值的委托。
QueueUserWorkItem(WaitCallback callBack, object state),WaitCallback是一个有一个object类型参数且无返回值的委托,state即WaitCallback中需要的参数, 不推荐这么使用,存在拆箱装箱的转换问题,影响性能。
1、Task出现背景
在前面的章节介绍过,Task出现之前,微软的多线程处理方式有:Thread→ThreadPool→委托的异步调用,虽然也可以基本业务需要的多线程场景,但它们在多个线程的等待处理方面、资源占用方面、线程延续和阻塞方面、线程的取消方面等都显得比较笨拙,在面对复杂的业务场景下,显得有点捉襟见肘了。正是在这种背景下,Task应运而生。
Task是微软在.Net 4.0时代推出来的,也是微软极力推荐的一种多线程的处理方式,Task看起来像一个Thread,实际上,它是在ThreadPool的基础上进行的封装,Task的控制和扩展性很强,在线程的延续、阻塞、取消、超时等方面远胜于Thread和ThreadPool。
Task复制文件应用参考:https://znlive.com/what-is-the-simplest-csharp-code-to-copy-directory
参考文章:
https://learn.microsoft.com/zh-cn/dotnet/standard/threading/threads-and-threading (推荐) 官网
https://blog.csdn.net/liyou123456789/article/details/120595489(推荐)重要
https://www.runoob.com/csharp/csharp-multithreading.html/https://www.w3schools.cn/cs/cs_multithreading.asp(推荐)
https://www.cnblogs.com/dotnet261010/p/6159984.html (可看可不看)
https://www.cnblogs.com/cheng8/p/16168128.html(可看可不看)
https://cloud.tencent.com/developer/article/1817868(可看可不看)
https://zhuanlan.zhihu.com/p/355962916(可看可不看)
二、虚方法和抽象方法
C#中的虚方法(Virtual Methods)和抽象方法(Abstract Methods)都与面向对象编程中的多态性和继承概念相关。它们允许您在基类中定义方法,然后在派生类中进行扩展或覆盖,以实现特定的行为。以下是对这两种方法的详细描述:
2.1 虚方法(Virtual Methods)
- 定义方式:虚方法是在基类中使用
virtual
关键字声明的方法。例如:
public class Animal { public virtual void MakeSound() { Console.WriteLine("Some generic animal sound -- from jhrs.com "); } }
- 派生类的重写:派生类可以使用
override
关键字重写虚方法,以提供特定于派生类的实现。例如:
public class Dog : Animal { public override void MakeSound() { Console.WriteLine("Bark -- from jhrs.com"); } }
- 多态性:虚方法支持多态性,这意味着您可以通过基类引用调用派生类中的方法。例如:
Animal myAnimal = new Dog(); myAnimal.MakeSound(); // 调用的是Dog类中的MakeSound方法
- 默认实现:虚方法可以提供一个默认的实现,但派生类可以选择是否要重写它。
- 可选重写:派生类可以选择性地重写虚方法。如果不重写,将调用基类中的实现。
2.2 抽象方法(Abstract Methods)
- 定义方式:抽象方法是在抽象类中声明的方法,并使用
abstract
关键字进行标记。抽象方法没有方法体。例如:
public abstract class Shape { public abstract double CalculateArea(); }
- 派生类的实现:派生类必须实现抽象方法,否则它们也必须声明为抽象类。
- 强制实现:抽象方法强制派生类提供特定的实现,以确保每个派生类都有自己的版本。
- 不能实例化:抽象类不能被直接实例化,只能用作基类。
- 多态性:抽象方法支持多态性,可以使用基类引用来调用派生类中的方法,就像虚方法一样。
总之,虚方法提供了默认的实现,允许派生类选择是否要重写它,而抽象方法则强制派生类提供特定的实现。抽象方法通常用于定义一组方法的接口,而虚方法用于提供默认实现并允许派生类进行自定义。
三、接口和抽象类
在C#中,接口和抽象类都是用于实现多态性和组件重用的机制,但它们有一些关键区别。下面是对C#中接口和抽象类的详细描述:
3.1 接口(Interface)
- 定义方式:接口是一种完全抽象的类型,定义了一组公共方法、属性、事件和索引器的契约,但没有实际的方法实现。接口使用
interface
关键字定义,不包含字段或构造函数。例如:
public interface IDrawable { void Draw(); }
- 多继承:一个类可以同时实现多个接口。这允许类在不同的上下文中扮演不同的角色。例如:
public class Circle : IDrawable, IResizable { // jhrs.com首发, 实现IDrawable和IResizable接口中的方法 }
- 无状态:接口不能包含字段(成员变量)或属性的实现。它们只能定义方法、属性、事件和索引器的契约。
- 强制实现:类实现接口时,必须提供接口中定义的所有成员的具体实现。这确保了接口中定义的行为在类中得以实现。
- 多态性:接口允许不同的类实现相同的接口,以便通过接口引用来调用不同类的方法。
3.2 抽象类(Abstract Class)
- 定义方式:抽象类是一个类,它可以包含抽象方法(使用
abstract
关键字定义的方法)和具体方法的实现。抽象类使用abstract
关键字定义。例如:
public abstract class Shape { public abstract double CalculateArea(); public void Display() { Console.WriteLine("Displaying the shape. -- from jhrs.com"); } }
- 单继承:C#中的类只能继承一个基类,因此抽象类实现了单一继承。
- 有状态:抽象类可以包含字段、属性、构造函数以及具体方法的实现。这使得抽象类可以包含一些通用数据和行为。
- 可选实现:派生类可以选择性地重写抽象方法,而不是强制要求提供所有抽象方法的实现。
- 多态性:抽象类允许多态性,允许派生类通过基类引用来调用方法,包括抽象方法和具体方法。
- 构造函数:抽象类可以有构造函数,但接口不能包含构造函数。
3.3 何时使用接口和抽象类
- 使用接口当您希望定义一组契约,以确保不同的类都实现相同的方法。
- 使用抽象类当您希望提供一些通用的实现,并为派生类提供一些自由度,同时实现多态性。
总之,接口和抽象类都是面向对象编程的重要概念,您应该根据您的设计需求选择适当的方式来实现多态性和组件重用。
四、依赖注入和控制反转
依赖注入(DI):服务的消费者利用一个独立的容器(Container)来获取所需的服务对象,容器自身在提供服务对象的过程中会自动完成依赖的解析与注入(核心功能:服务注册和服务提供)
通俗来讲,就是把有依赖关系的类放到容器中,然后在我们需要这些类时,容器自动解析出这些类的实例。
依赖注入最大的好处时实现类的解耦,利于程序拓展、单元测试、自动化模拟测试等。
控制反转(IOC): 控制反转只是一个概念一种设计思想,也就是将创建对象实例的控制权(原本是程序员)从代码控制权剥离到 `IOC 容器` 中控制。
依赖倒置原则(DIP)
「依赖倒置」是本文要讲述的主要内容,是七大设计原则之二,在生产实际中应用的非常广泛,主要内容为
1. 高层模块(high-level modules)**不要直接依赖**低层模块(low-level);
2. 高层模块和低层模块应该**通过抽象(abstractions)来互相依赖**;
3. 抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。
在.NET Core中DI的核心分为两个组件:IServiceCollection和 IServiceProvider。
– IServiceCollection 负责注册
– IServiceProvider 负责提供实例
依赖注入的三种生命周期:
– Transient: 每一次GetService都会创建一个新的实例
– Scoped: 在同一个Scope内只初始化一个实例 ,可以理解为( 每一个request级别只创建一个实例,同一个http request会在一个 scope内)
– Singleton :整个应用程序生命周期以内只创建一个实例
注入方式
一 、构造方法注入
目前构造方法注入是依赖注入推荐使用方式。
– 优点
– 在构造方法中体现出对其他类的依赖,一眼就能看出这个类需要依赖哪些类才能工作
– 脱离了 IOC 框架,这个类仍然可以工作,POJO 的概念
– 一旦对象初始化成功了,这个对象的状态肯定是正确的
– 缺点
– 构造函数会有很多参数(Bad smell)
– 有些类是需要默认构造函数的,比如 MVC 框架的 Controller 类,一旦使用构造函数注入,就无法使用默认构造函数
– 这个类里面的有些方法并不需要用到这些依赖(Bad smell)
二 、属性方式注入
通过属性方式注入容易和类的实例属性混淆,不建议使用。
– 优点
– 在对象的整个生命周期内,可以随时动态的改变依赖
– 非常灵活
– 缺点
– 对象在创建后,被设置依赖对象之前这段时间状态是不对的
– 不直观,无法清晰地表示哪些属性是必须的
三 、方法参数注入
方法参数注入的意思是在创建对象后,通过自动调用某个方法来注入依赖。
– 优点:
– 比较灵活
– 缺点:
– 新加入依赖时会破坏原有的方法签名,如果这个方法已经被其他很多模块用到就很麻烦
– 与构造方法注入一样,会有很多参数
IOC/DI的优缺点:
优点:
1、它有助于类的解耦。
2、由于解耦,代码的可重用性增加。
3、改进了代码可维护性和测试。
缺点
– 目前主流的 `IOC/DI` 基本采用反射的方式来实现依赖注入,在一定程度会影响性能
参考文章:
https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-6.0
https://learn.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection#service-lifetimes
https://www.cnblogs.com/cn-star/p/14699484.html
https://www.cnblogs.com/cwsheng/p/14224423.html
https://www.cnblogs.com/jesse2013/p/di-in-aspnetcore.html
https://www.cnblogs.com/stulzq/p/12610026.html
五、中间件和过滤器
1、中间件
中间件是一种装配到应用管道以处理请求和响应的软件(应用程序管道中的一个组件,用来拦截请求过程进行一些其他处理和响应)。 每个组件:
– 选择是否将请求传递到管道中的下一个组件。
– 可在管道中的下一个组件前后执行工作。
2、中间件的四种运行方式
1、app.Use(),IApplicationBuilder接口原生提供,注册等都用它。
2、app.Run(),是一个扩展方法,它需要一个RequestDelegate委托,里面包含了Http的上下文信息,没有next参数,因为它总是在管道最后一步执行。
3、app.Map(),也是一个扩展方法,类似于MVC的路由,用途一般是一些特殊请求路径的处理。
4、app.UseMiddleware<>(),没错,就是这个了。 为什么说功能强大呢?是因为它不但提供了注册中间件的功能,还提供了依赖注入(DI)的功能,以后大部分情况就用它了。
asp.net core` 提供了`IApplicationBuilder`接口来让把中间件注册到`asp.net`的管道请求当中去,中间件是一个典型的AOP应用。 下面是一个微软官方的一个中间件管道请求图:
3、中间件(Middleware)和过滤器(Filter)的区别
熟悉MVC框架的同学应该知道,MVC也提供了5大过滤器供我们用来处理请求前后需要执行的代码。
分别是:AuthenticationFilter, AuthorizationFilter,ActionFilter, ExceptionFilter,ResultFilter。
根据描述,可以看出中间件和过滤器的功能类似,那么他们有什么区别?为什么又要搞一个中间件呢?
其实,过滤器和中间件他们的关注点是不一样的,也就是说职责不一样,干的事情就不一样。
同作为两个AOP利器,过滤器更贴合业务,它关注于应用程序本身,比如你看ActionFilter 和 ResultFilter,它是直接与控制器交互的,同时比如我们要对结果进行格式化啦,且要对请求的ViewModel进行数据验证啦,这个时候就是用Filter无疑了。它是MVC的一部分,它可以拦截到你控制器上下文的一些信息,而中间件是没有这个能力的。
中间件ASP.NET Core这个基础提供的功能,而Filter是ASP.NET Core MVC中提供的功能,ASP.NET Core MVC是由MVC中间件提供的框架,而Fiter属于MVC中间件提供的功能。
4、什么情况我们需要中间件
在我们的应用程序当中和业务关系不大的一些需要在管道中做的事情可以使用,比如身份验证,`Session`存储,日志记录等。
其实我们的 asp.net core项目中本身已经包含了很多个中间件,比如MVC本身就是一个中间件,过滤器是中间件的一部分。
参考文章:
https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0(官网)
六、委托和事件
委托定义:[委托](https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/builtin-types/reference-types)是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时,你可以将其实例与任何具有兼容参数和返回类型的方法进行绑定。 你可以通过委托实例调用方法。
简单的理解,委托是方法的抽象类,它定义了方法的类型,可以实例化。和普通的类一样,可以申明变量进行赋值,可以当作参数传递,可以定义成属性。
委托具有以下属性:
– 委托类似于 C++ 函数指针,但委托完全面向对象,不像 C++ 指针会记住函数,委托会同时封装对象实例和方法。
– 委托允许将方法作为参数进行传递。
– 委托可用于定义回调方法。
– 委托可以链接在一起;例如,可以对一个事件调用多个方法。
– 方法不必与委托类型完全匹配。 有关详细信息,请参阅[使用委托中的变体](https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/covariance-contravariance/using-variance-in-delegates)。
– 使用 Lambda 表达式可以更简练地编写内联代码块。 Lambda 表达式(在某些上下文中)可编译为委托类型。 若要详细了解 lambda 表达式,请参阅 [lambda 表达式](https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/lambda-expressions)。
事件定义:事件是基于委托的,为委托提供一个订阅或发布的机制,事件就相当于保存委托的数组,实际上,事件是建立在对委托的语言支持之上的一种设计而已。
总的来说
1.委托是一个类,可以实例化,通过委托的构造函数把方法赋值给委托实例;
2.触发委托有两种方式:委托实例.Invoke(参数列表)和委托实例(参数列表);
3.事件可看作是一个委托类型的变量;
4.+=为事件注册多个委托实例或多个方法;
5.-=为事件减少多个委托实例或多个方法;
5.EventHandler是一个委托。
委托与事件区别
通俗的解释:
1、事件的声明只是在委托前面加一个event关键词,虽然你可以定义一个public,但是有了event关键词后编译器始终会把这个委托声明为private,然后添加1组add,remove方法。add对应+=,remove对应-=。这样就导致事件只能用+=,-=来绑定方法或者取消绑定方法。而委托可以用=来赋值,当然委托也是可以用+=,-=来绑定方法的。 (保护委托字段,对外不开放,所以外部对象没法直接操作委托。提供了Add和Remove方法,供外部对象订阅事件和取消事件)
2、委托可以在外部被其他对象调用,而且可以有返回值(返回最后一个注册方法的返回值)。而事件不可以在外部调用,只能在声明事件的类内部被调用。
事件基于委托,但并非委托。(事件的处理方法在对象外部定义,而事件的执行是在对象的内部,至于事件的触发,何时何地无所谓。)
1.事件只能在方法的外部进行声明,而委托在方法的外部和内部都可以进行声明;
2.事件只能在类的内部进行触发,不能在类的外部进行触发。而委托在类的内部和外部都可触发;
3.委托一般用于回调,而事件一般用于外部接口。在观察者模式中,被观察者可在内部声明一个事件作为外部观察者注册的接口。
参考文章:
https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/delegates/
https://www.cnblogs.com/wangqiang3311/p/11201647.html
https://www.cnblogs.com/Bob-luo/archive/2023/02/17/17129790.html
https://zhuanlan.zhihu.com/p/398084318
七、DDD(领域驱动设计)
领域驱动设计(Domain-Driven Design,简称DDD)是一种软件开发方法,旨在将业务领域的复杂性和需求直观地映射到软件代码中,以便更好地理解、设计和维护复杂的应用程序。DDD强调将软件开发过程中的关注点从技术转移到业务领域,并提供了一组概念和模式来实现这一目标。DDD是一套思想方法论,核心在于 建模 ,提供了一套 落地的一些实践,核心在于战略设计。
以下是一些与DDD相关的关键概念:
- 实体(Entity):实体是业务领域中具有标识的对象,通常对应于数据库中的表。实体具有生命周期,可以具有状态和行为。例如,一个订单(Order)可以是一个实体。
- 值对象(Value Object):值对象是没有自己的标识,通常是不可变的对象,用于表示概念上的值。例如,订单中的金额可以作为值对象。
- 聚合根(Aggregate Root):聚合根是实体的根,它负责管理相关实体和值对象。它充当访问和维护整个聚合的入口点。
- 仓储(Repository):仓储是用于访问和持久化领域对象的接口,它隐藏了数据访问细节,提供了对领域对象的抽象访问方式。
- 领域事件(Domain Events):领域事件是在领域中发生的事件,它们用于表示领域的状态变化,通常与业务规则和行为相关。
- 领域服务(Domain Services):领域服务是一种不属于实体或值对象的对象,它包含与特定领域相关的业务逻辑。
下面是一个简单的C#示例,演示如何使用DDD的一些基本概念:
// 实体类-首发于jhrs.com(江湖人士网) public class Order { public int Id { get; private set; } public decimal TotalAmount { get; private set; } // 构造函数 public Order(int id, decimal totalAmount) { Id = id; TotalAmount = totalAmount; } // 领域事件 public void MarkAsPaid() { // 发出领域事件 DomainEvents.Publish(new OrderPaidEvent(this)); } } // 值对象类-首发于jhrs.com(江湖人士网) public class Money { public decimal Amount { get; private set; } public string Currency { get; private set; } public Money(decimal amount, string currency) { Amount = amount; Currency = currency; } } // 领域事件类-首发于jhrs.com(江湖人士网) public class OrderPaidEvent { public Order Order { get; private set; } public OrderPaidEvent(Order order) { Order = order; } } // 领域事件发布器-首发于jhrs.com(江湖人士网) public static class DomainEvents { public static event Action<object> OnDomainEvent; public static void Publish(object domainEvent) { OnDomainEvent?.Invoke(domainEvent); } } // 使用示例-首发于jhrs.com(江湖人士网) public class Program { public static void Main() { var order = new Order(1, new Money(100, "USD")); order.MarkAsPaid(); // 订阅领域事件 DomainEvents.OnDomainEvent += HandleOrderPaidEvent; } public static void HandleOrderPaidEvent(object domainEvent) { if (domainEvent is OrderPaidEvent orderPaidEvent) { Console.WriteLine($"jhrs.com友情提示:狗日的,订单ID{orderPaidEvent.Order.Id}已经支付了,你个哈麻屁又来喊我付款,老子遇到你了。"); } } }
最新高级C#面试知识点
这个示例演示了一个订单(Order)实体、值对象(Money)、领域事件(OrderPaidEvent)和领域事件发布器(DomainEvents)的基本概念。DDD的核心思想是将业务领域的复杂性进行建模,使代码更加清晰、可维护和易于理解。在实际应用中,DDD可能涉及更复杂的业务规则、聚合和仓储等概念。
八、浅拷贝和深拷贝
所谓的对象拷贝(复制)就是为对象创建副本,得到相同的对象。
深拷贝:指的是拷贝一个对象时,**不仅仅把对象的引用进行拷贝,还把该对象引用的值也一起拷贝。这样进行深拷贝后的副本对象就和源对象互相独立**,其中任何一个的成员对象改动都不会对另外一个成员对象造成影响。
完全将对象中的所有字段(引用类型和值类型等)都复制到副本对象中,这些字段都会被重新创建并且复制,副本对象内的值并不会因为源对象数据的值的修改而跟着发生改变。**(也就是说深拷贝出来的副本对象中,对象里的数据如果是值类型,栈内容是其值本身;对于引用类型,其值是托管堆中保存的具体的值,而不是托管堆的内存地址。因此对拷贝出来的副本对象的修改不会反映到被拷贝的源对象上。深拷贝本质上就是软件设计模式里的原型模式。与C#对应的接口是ICloneable。)
浅拷贝: 仅仅**把对象的引用进行拷贝,但是拷贝对象和源对象还是引用同一份实体**。此时,其中一个的成员对象的改变都会影响到另一个的成员对象。
简单的复制栈的内容,对于值类型,栈内容是其值本身,对于引用类型,其值为托管堆的内存地址,对拷贝的对象的修改会反映到被拷贝的对象。(同样都是将对象中的所有字段都复制到副本对象中,副本对象里的数据如果是值类型,栈内容是其值本身,在源数据内修改值类型,副本的值类型不会发生改变,因为值类型本身在栈内有不同的地址。但是如果副本对象的数据是引用类型,由于浅拷贝只是拷贝引用类型值的引用,也就是堆的地址,所以副本对象的引用类型数据发生改变时,源对象中的引用类型数据也会跟着改变。)
C#实现浅拷贝:**调用MemberwiseClone方法**,创建一个新的对象,然后复制当前对象的非静态字段的新对象创建一个浅表副本。
C#深拷贝的有三种实现:
1. **反射**
2. **序列化**
3. **表达式树**
参考文章:
https://zhuanlan.zhihu.com/p/349094034
https://www.cnblogs.com/abeam/p/11919838.html
https://www.cnblogs.com/dotnet261010/p/12329220.html
https://www.cnblogs.com/SF8588/p/16152078.html
https://blog.csdn.net/ananlele_/article/details/108126267
九、什么是泛型
定义:是将不确定的类型预先定义下来的一种C#高级语法,泛型引入了类型参数的概念,类似于C++中的模板,泛型将类型参数的概念引入 .NET,这样就可设计具有以下特征的类和方法:在客户端代码声明并初始化这些类或方法之前,这些类或方法会延迟指定一个或多个类型。例如,通过使用泛型类型参数 `T`,可以编写其他客户端代码能够使用的单个类,而不会产生运行时转换或装箱操作的成本或风险。(类型参数化)
**泛型类和泛型方法兼具可重用性、类型安全性和效率,这是非泛型类和非泛型方法无法实现的**。
泛型更强调一种范式,时间、空间上其实都没省,提升了代码的复用率减少冗余量。
###### 泛型(Generic)的特性
使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:
– 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
– 您可以创建泛型集合类。.NET 框架类库在 *System.Collections.Generic* 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 *System.Collections* 中的集合类。
– 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
– 您可以对泛型类进行约束以访问特定数据类型的方法。
– 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
泛型类型约束:
where约束:泛型定义中的 `where` 子句指定对用作泛型类型、方法、委托或本地函数中类型参数的参数类型的约束。 约束可指定接口、基类或要求泛型类型为引用、值或非托管类型。 约束声明类型参数必须具有的功能,并且约束必须位于任何声明的基类或实现的接口之后。
`new` 约束指定泛型类或方法声明中的类型实参必须有公共的无参数构造函数。 若要使用 `new` 约束,则该类型不能为抽象类型。
参考文章:
https://learn.microsoft.com/zh-cn/dotnet/csharp/fundamentals/types/generics
https://learn.microsoft.com/zh-cn/dotnet/csharp/programming-guide/generics/generic-methods
https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/where-generic-type-constraint
https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/where-generic-type-constraint
https://www.cnblogs.com/dotnet261010/p/9034594.html
https://www.runoob.com/csharp/csharp-generic.html
https://zhuanlan.zhihu.com/p/262002365
十、GC(垃圾回收)
C#的垃圾回收机制(Garbage Collection,简称GC)是一种自动内存管理系统,用于在运行时跟踪和回收不再使用的对象,以释放内存资源并防止内存泄漏。C#的垃圾回收机制允许开发人员专注于应用程序的逻辑,而无需显式地管理内存。
以下是C#垃圾回收机制的关键概念和工作原理:
- 垃圾回收器(Garbage Collector):垃圾回收器是.NET运行时的一部分,负责监视和管理内存中的对象。它定期检测不再被引用的对象,并回收这些对象所占用的内存。
- 对象的生命周期:每个在C#中创建的对象都有一个生命周期。当对象不再被任何引用变量引用时,它变成垃圾对象,等待垃圾回收器的处理。
- 引用计数和可达性分析:C#垃圾回收器使用可达性分析(Reachability Analysis)来确定对象是否可达。这意味着它从根对象开始,追踪对象之间的引用链,找出哪些对象是可达的。不可达的对象被标记为垃圾。
- 代和世代回收:垃圾回收器将堆内存分成不同的代。新创建的对象放在第0代(Generation 0)中。当第0代满时,垃圾回收器会检查并回收不可达的对象。如果对象在第0代经历了多次垃圾回收而仍然存活,它将升级到第1代(Generation 1)或第2代(Generation 2),并且垃圾回收的频率会降低,以提高性能。
- 垃圾回收算法:C#垃圾回收器使用一种叫做标记-清除(Mark and Sweep)的算法。它首先标记所有可达的对象,然后清除不可达的对象,最终压缩内存中的对象,以便更好地管理内存碎片。
- 终结器(Finalizers):C#允许对象定义终结器,它们是特殊的方法,会在对象被销毁之前被调用。通常,终结器用于释放非托管资源,例如文件句柄。垃圾回收器在处理终结器时会进行额外的工作。
- 弱引用(Weak References):C#允许创建弱引用,这些引用不会阻止对象被垃圾回收。这对于需要跟踪对象的生命周期但不希望阻止其回收的情况很有用。
- 手动内存管理:虽然C#的垃圾回收机制自动处理大多数内存管理任务,但开发人员仍然可以通过
IDisposable
接口和using
语句来显式释放托管和非托管资源,以提高性能和资源的可预测性释放。
总的来说,C#的垃圾回收机制通过监视对象的可达性和管理内存回收,帮助开发人员避免内存泄漏和手动内存管理的复杂性。这使得开发更容易,同时确保应用程序的内存资源得以有效利用。
10.1 服务器GC和客户机GC区别
C#的垃圾回收(Garbage Collection,GC)在Windows服务器和普通桌面客户机上的基本工作原理是相同的,因为它们都是基于.NET运行时的。然而,在实际应用中,有一些区别需要考虑:
- 资源和性能要求:
- 服务器应用程序通常需要处理更高的负载和大量的并发请求,因此对于服务器端应用来说,垃圾回收的性能和资源利用是关键。服务器GC(Server Garbage Collection)在服务器上进行了优化,它使用多个线程和堆段(heap segments)来提高回收性能。
- 桌面应用程序通常面临较低的负载,因此对于桌面应用来说,性能要求通常较低。它们使用工作站GC(Workstation Garbage Collection),它的性能目标相对较低。
- GC模式:
- 服务器GC在多核服务器上默认启用,并支持并发GC。它试图最小化应用程序的停滞时间,以支持高并发负载。
- 桌面GC通常是基于工作站的,性能较低,适合一般的桌面应用程序,但不适用于高并发服务器应用。
- 垃圾回收的频率:
- 服务器GC通常会更频繁地触发,以确保在高负载条件下有效管理内存。这可以在某种程度上增加停滞时间,但有助于维护可用性。
- 桌面GC通常会更少地触发,以减少对桌面应用程序的干扰。
- 垃圾回收的配置:
- 开发人员可以通过配置来自定义垃圾回收行为,包括在服务器和桌面环境中。配置可以影响堆大小、并发度、回收触发频率等方面。
- 内存消耗:
- 服务器GC可能会使用更多的内存来优化性能,因为服务器通常有更多的可用内存。
- 桌面GC会更加节约内存,因为桌面应用通常面临有限的内存资源。
总之,虽然C#的垃圾回收在服务器和桌面环境中的基本工作原理相同,但在性能和配置上存在差异,以满足不同类型应用的需求。开发人员应根据应用程序的具体要求来选择合适的GC配置和策略。
十一、分布式锁
在C#中实现分布式锁通常需要使用外部的分布式锁服务或者分布式锁库,因为分布式锁需要在多个应用程序实例之间协调。一种常见的方式是使用ZooKeeper、etcd、Redis等分布式系统,或者使用专门的分布式锁库,如DistributedLock等。
以下是一个简单的C#示例,演示如何使用Redis实现分布式锁:
using StackExchange.Redis; using System; using System.Threading; public class DistributedLockExample { private static IDatabase redisDatabase; private static string lockKey = "myLock"; private static string lockValue = Guid.NewGuid().ToString(); private static TimeSpan lockTimeout = TimeSpan.FromSeconds(30); public static void Main(string[] args) { // 连接到Redis服务器 ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("YourRedisConnectionString"); redisDatabase = redis.GetDatabase(); // 尝试获取分布式锁 if (AcquireLock()) { try { // 在这里执行需要加锁的操作 Console.WriteLine("Lock acquired! Performing some work..."); Thread.Sleep(TimeSpan.FromSeconds(10)); } finally { // 释放锁 ReleaseLock(); Console.WriteLine("Lock released."); } } else { Console.WriteLine("Failed to acquire lock. Another process is holding the lock."); } // 关闭Redis连接 redis.Close(); } private static bool AcquireLock() { return redisDatabase.StringSet(lockKey, lockValue, lockTimeout, When.NotExists); } private static void ReleaseLock() { var luaScript = LuaScript.Prepare("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"); luaScript.Execute(redisDatabase, new { lockKey }, new { lockValue }); } }
最新高级C#面试知识点
在上面的示例中,我们首先连接到Redis服务器,然后使用AcquireLock
方法尝试获取分布式锁。如果成功获取锁,我们执行需要加锁的操作,然后使用ReleaseLock
方法释放锁。如果无法获取锁,表示其他进程已经持有锁。
这只是一个简单示例,实际中你可能需要更多的错误处理和异常处理,以确保分布式锁的可靠性。你还可以根据具体的需求调整锁的超时时间、重试策略等参数。
十二、lock(锁)
在C#中,lock
是一种用于多线程编程的关键字,它用于实现互斥锁(Mutex),确保一段代码在同一时刻只能由一个线程执行,从而避免多线程竞争条件导致的数据不一致或并发问题。lock
关键字基于.NET的监视器(Monitor)类实现了互斥锁。
以下是关于lock
关键字的一些重要特点和用法:
- 使用方式:
lock
关键字通常与Monitor
类的Enter
和Exit
方法一起使用,用于创建一个代码块,这个代码块在同一时刻只能被一个线程执行。通常,你会锁定一个对象,以确保在不同线程之间访问共享资源时的互斥。
object lockObject = new object(); lock (lockObject) { // 互斥的代码块 jhrs.com 示例代码 }
- 锁定的对象:
lock
关键字需要提供一个对象来进行锁定。这个对象通常是一个引用类型变量,可以是任何对象,但通常用于锁定共享资源。不同线程锁定同一个对象,以确保互斥。 - 隐式释放锁:
lock
关键字不需要显式释放锁,它会在代码块执行完毕后自动释放锁。这确保了即使发生异常,锁也会被正确释放。 - 递归锁:C# 的
lock
关键字支持递归锁,这意味着同一线程可以多次获取同一个锁,而不会导致死锁。但要小心使用递归锁,确保在合适的情况下使用它。 - 性能注意事项:使用
lock
可能会引入性能开销,因为它会导致线程阻塞等待锁的释放。在某些情况下,可以考虑使用更轻量级的同步原语,如Monitor
、Mutex
、Semaphore
或ReaderWriterLockSlim
,以根据具体需求来提高性能。
lock
关键字是C#中管理多线程并发的一种简单而有效的方式,但需要小心使用,以避免潜在的死锁和性能问题。在编写多线程应用程序时,确保正确使用锁来保护共享资源是至关重要的。
十三、设计模式
一共有 23 种常见的设计模式,它们分为三类:创建型、结构型和行为型。以下是这些设计模式的代码示例以及使用场景。
创建型设计模式 (Creational Patterns):
- 单例模式 (Singleton Pattern):
- 使用场景:当需要确保一个类只有一个实例,并提供一个全局访问点时,如日志记录器、数据库连接池等。
public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton Instance { get { if (instance == null) { instance = new Singleton(); } return instance; } } }
- 工厂方法模式 (Factory Method Pattern):
- 使用场景:当需要根据特定条件创建不同类型的对象,如UI组件的创建、日志记录器的选择等。
public abstract class Product { public abstract void Create(); } public class ConcreteProductA : Product { public override void Create() { Console.WriteLine("Product A is created. --from jhrs.com "); } } public class ConcreteProductB : Product { public override void Create() { Console.WriteLine("Product B is created.--from jhrs.com "); } } public abstract class Creator { public abstract Product FactoryMethod(); } public class ConcreteCreatorA : Creator { public override Product FactoryMethod() { return new ConcreteProductA(); } }
- 抽象工厂模式 (Abstract Factory Pattern):
- 使用场景:当需要一次性创建一组相关的对象,如创建操作系统相关的UI组件、数据库访问的抽象等。
public interface IProductA { void Create(); } public interface IProductB { void Create(); } public class ConcreteProductA1 : IProductA { public void Create() { Console.WriteLine("Product A1 is created."); } } public class ConcreteProductA2 : IProductA { public void Create() { Console.WriteLine("Product A2 is created."); } } public class ConcreteProductB1 : IProductB { public void Create() { Console.WriteLine("Product B1 is created."); } } public class ConcreteProductB2 : IProductB { public void Create() { Console.WriteLine("Product B2 is created."); } } public interface IAbstractFactory { IProductA CreateProductA(); IProductB CreateProductB(); } public class ConcreteFactory1 : IAbstractFactory { public IProductA CreateProductA() { return new ConcreteProductA1(); } public IProductB CreateProductB() { return new ConcreteProductB1(); } }
- 建造者模式 (Builder Pattern):
- 使用场景:当需要构建一个复杂对象,且需要分步骤来构建不同部分时,如创建文档、报告等。
public class Product { public string PartA { get; set; } public string PartB { get; set; } } public abstract class Builder { public abstract void BuildPartA(); public abstract void BuildPartB(); public abstract Product GetResult(); } public class ConcreteBuilder : Builder { private Product product = new Product(); public override void BuildPartA() { product.PartA = "Part A--from jhrs.com "; } public override void BuildPartB() { product.PartB = "Part B--from jhrs.com "; } public override Product GetResult() { return product; } } public class Director { public void Construct(Builder builder) { builder.BuildPartA(); builder.BuildPartB(); } }
- 原型模式 (Prototype Pattern):
- 使用场景:当需要复制对象而不暴露其具体构造细节时,如创建游戏中的角色、克隆对象等。
public interface ICloneable { ICloneable Clone(); } public class ConcretePrototype : ICloneable { public int Number { get; set; } public ICloneable Clone() { return new ConcretePrototype { Number = this.Number }; } }
结构型设计模式 (Structural Patterns):
- 适配器模式 (Adapter Pattern):
- 使用场景:当需要将一个类的接口转换为另一个接口以满足客户端的期望接口时,如在新的库中使用旧的类。
public class Adaptee { public void SpecificRequest() { Console.WriteLine("Adaptee's specific request.--from jhrs.com "); } } public interface ITarget { void Request(); } public class Adapter : ITarget { private Adaptee adaptee = new Adaptee(); public void Request() { adaptee.SpecificRequest(); } }
- 桥接模式 (Bridge Pattern):
- 使用场景:当需要将抽象部分与实现部分分离,以便它们可以独立地变化时,如不同操作系统上的图形用户界面。
public interface IImplementor { void OperationImpl(); } public abstract class Abstraction { protected IImplementor implementor; public Abstraction(IImplementor implementor) { this.implementor = implementor; } public abstract void Operation(); } public class ConcreteImplementorA : IImplementor { public void OperationImpl() { Console.WriteLine("Concrete Implementor A operation.--from jhrs.com "); } } public class ConcreteImplementorB : IImplementor { public void OperationImpl() { Console.WriteLine("Concrete Implementor B operation."); } } public class RefinedAbstraction : Abstraction { public RefinedAbstraction(IImplementor implementor) : base(implementor) { } public override void Operation() { implementor.OperationImpl(); } }
- 组合模式 (Composite Pattern):
- 使用场景:当需要将对象组织成树形结构以表示部分-整体层次关系,并希望客户端统一处理单个对象和组合对象时,如图形用户界面中的控件树。
public abstract class Component { public string Name { get; set; } public Component(string name) { Name = name; } public abstract void Display(int depth); } public class Leaf : Component { public Leaf(string name) : base(name) { } public override void Display(int depth) { Console.WriteLine(new string('-', depth) + Name); } } public class Composite : Component { private List<Component> children = new List<Component>(); public Composite(string name) : base(name) { } public void Add(Component component) { children.Add(component); } public override void Display(int depth) { Console.WriteLine(new string('-', depth) + Name); foreach (var child in children) { child.Display(depth + 2); } } }
- 装饰器模式 (Decorator Pattern):
- 使用场景:当需要动态地为对象添加额外的职责时,而不影响其他对象时,如窗口系统中的窗口装饰。
public abstract class Component { public abstract void Operation(); } public class ConcreteComponent : Component { public override void Operation() { Console.WriteLine("Concrete Component operation.--from jhrs.com "); } } public abstract class Decorator : Component { protected Component component; public Decorator(Component component) { this.component = component; } public override void Operation() { if (component != null) { component.Operation(); } } } public class ConcreteDecoratorA : Decorator { public ConcreteDecoratorA(Component component) : base(component) { } public override void Operation() { base.Operation(); Console.WriteLine("Concrete Decorator A operation."); } } public class ConcreteDecoratorB : Decorator { public ConcreteDecoratorB(Component component) : base(component) { } public override void Operation() { base.Operation(); Console.WriteLine("Concrete Decorator B operation.--from jhrs.com "); } }
- 外观模式 (Facade Pattern):
- 使用场景:当需要提供一个简化的接口,隐藏复杂的子系统,以便客户端可以更轻松地与子系统交互时,如操作系统中的启动和关机功能。
public class SubsystemA { public void OperationA() { Console.WriteLine("Subsystem A operation.--from jhrs.com "); } } public class SubsystemB { public void OperationB() { Console.WriteLine("Subsystem B operation.--from jhrs.com "); } } public class Facade { private SubsystemA subsystemA; private SubsystemB subsystemB; public Facade() { subsystemA = new SubsystemA(); subsystemB = new SubsystemB(); } public void Operation() { subsystemA.OperationA(); subsystemB.OperationB(); } }
最新高级C#面试知识点
- 享元模式 (Flyweight Pattern):
- 使用场景:当需要共享大量细粒度的对象,以节省内存和提高性能时,如文本编辑器中的字符缓存。
public interface IFlyweight { void Operation(string extrinsicState); } public class ConcreteFlyweight : IFlyweight { public string IntrinsicState { get; private set; } public ConcreteFlyweight(string intrinsicState) { IntrinsicState = intrinsicState; } public void Operation(string extrinsicState) { Console.WriteLine($"Flyweight with Intrinsic State '{IntrinsicState}' and Extrinsic State '{extrinsicState}'--from jhrs.com "); } } public class FlyweightFactory { private Dictionary<string, IFlyweight> flyweights = new Dictionary<string, IFlyweight>(); public IFlyweight GetFlyweight(string key) { if (!flyweights.ContainsKey(key)) { flyweights[key] = new ConcreteFlyweight(key); } return flyweights[key]; } }
最新高级C#面试知识点
行为型设计模式 (Behavioral Patterns):
- 策略模式 (Strategy Pattern):
- 使用场景:当需要在运行时选择不同算法或行为时,如排序算法、支付方式的选择。
public interface IStrategy { void Execute(); } public class ConcreteStrategyA : IStrategy { public void Execute() { Console.WriteLine("Strategy A is executed.--from jhrs.com "); } } public class ConcreteStrategyB : IStrategy { public void Execute() { Console.WriteLine("Strategy B is executed.--from jhrs.com "); } } public class Context { private IStrategy strategy; public Context(IStrategy strategy) { this.strategy = strategy; } public void SetStrategy(IStrategy strategy) { this.strategy = strategy; } public void ExecuteStrategy() { strategy.Execute(); } }
- 状态模式 (State Pattern):
- 使用场景:当对象的行为取决于其内部状态,并且需要在状态转换时改变其行为时,如自动售货机中的不同销售状态。
public interface IState { void Handle(); } public class ConcreteStateA : IState { public void Handle() { Console.WriteLine("State A is handling.--from jhrs.com "); } } public class ConcreteStateB : IState { public void Handle() { Console.WriteLine("State B is handling.--from jhrs.com "); } } public class Context { private IState state; public Context() { state = new ConcreteStateA(); } public void ChangeState(IState newState) { state = newState; } public void Request() { state.Handle(); } }
- 观察者模式 (Observer Pattern):
- 使用场景:当一个对象(主题)状态的改变需要通知其他对象(观察者)并保持松耦合时,如事件处理、消息传递系统。
public interface IObserver { void Update(string message); } public class ConcreteObserver : IObserver { private string name; public ConcreteObserver(string name) { this.name = name; } public void Update(string message) { Console.WriteLine($"{name} received message: {message}--from jhrs.com "); } } public class Subject { private List<IObserver> observers = new List<IObserver>(); public void Attach(IObserver observer) { observers.Add(observer); } public void Notify(string message) { foreach (var observer in observers) { observer.Update(message); } } }
- 备忘录模式 (Memento Pattern):
- 使用场景:当需要捕获和恢复对象的内部状态时,如文本编辑器中的撤销和重做功能。
public class Memento { public string State { get; } public Memento(string state) { State = state; } } public class Originator { public string State { get; set; } public Memento CreateMemento() { return new Memento(State); } public void RestoreMemento(Memento memento) { State = memento.State; } } public class Caretaker { public Memento Memento { get; set; } }
- 命令模式 (Command Pattern):
- 使用场景:当需要将请求封装成对象,以便可以在不同时间执行和排队请求,如遥控器中的按钮操作。
public interface ICommand { void Execute(); } public class Receiver { public void Action() { Console.WriteLine("Receiver is performing an action.--from jhrs.com "); } } public class ConcreteCommand : ICommand { private Receiver receiver; public ConcreteCommand(Receiver receiver) { this.receiver = receiver; } public void Execute() { receiver.Action(); } } public class Invoker { private ICommand command; public void SetCommand(ICommand command) { this.command = command; } public void ExecuteCommand() { command.Execute(); } }
- 中介者模式 (Mediator Pattern):
- 使用场景:当多个对象之间需要相互通信,但不想让对象之间相互耦合时,如聊天室中的用户通信。
public interface IMediator { void Send(string message, Colleague colleague); } public class ConcreteMediator : IMediator { private Colleague colleague1; private Colleague colleague2; public void SetColleague1(Colleague colleague) { colleague1 = colleague; } public void SetColleague2(Colleague colleague) { colleague2 = colleague; } public void Send(string message, Colleague colleague) { if (colleague == colleague1) { colleague2.Receive(message); } else { colleague1.Receive(message); } } } public abstract class Colleague { protected IMediator mediator; public Colleague(IMediator mediator) { this.mediator = mediator; } public abstract void Send(string message); public abstract void Receive(string message); }
- 访问者模式 (Visitor Pattern):
- 使用场景:当需要在不修改对象结构的情况下添加新操作时,如编译器中的抽象语法树遍历。
public interface IElement { void Accept(IVisitor visitor); } public class ConcreteElementA : IElement { public void Accept(IVisitor visitor) { visitor.VisitConcreteElementA(this); } } public class ConcreteElementB : IElement { public void Accept(IVisitor visitor) { visitor.VisitConcreteElementB(this); } } public interface IVisitor { void VisitConcreteElementA(ConcreteElementA element); void VisitConcreteElementB(ConcreteElementB element); } public class ConcreteVisitor : IVisitor { public void VisitConcreteElementA(ConcreteElementA element) { Console.WriteLine("Visitor is processing ConcreteElementA.--from jhrs.com "); } public void VisitConcreteElementB(ConcreteElementB element) { Console.WriteLine("Visitor is processing ConcreteElementB.--from jhrs.com "); } }
最新高级C#面试知识点
- 解释器模式 (Interpreter Pattern):
- 使用场景:当需要定义语言的语法解析,并且可以解释和执行语言的表达式时,如编译器和解释器。
public abstract class AbstractExpression { public abstract void Interpret(Context context); } public class TerminalExpression : AbstractExpression { public override void Interpret(Context context) { Console.WriteLine("Terminal expression is interpreted.--from jhrs.com "); } } public class NonTerminalExpression : AbstractExpression { public override void Interpret(Context context) { Console.WriteLine("Non-terminal expression is interpreted.--from jhrs.com "); } } public class Context { public string Input { get; set; } }
最新高级C#面试知识点
- 迭代器模式 (Iterator Pattern):
- 使用场景:当需要提供一种顺序访问聚合对象中的元素的方法,而又不暴露其内部表示时,如集合类中的迭代器。
public interface IIterator { bool HasNext(); object Next(); } public interface IAggregate { IIterator CreateIterator(); } public class ConcreteIterator : IIterator { private List<object> items; private int position = 0; public ConcreteIterator(List<object> items) { this.items = items; } public bool HasNext() { return position < items.Count; } public object Next() { return items[position++]; } } public class ConcreteAggregate : IAggregate { private List<object> items = new List<object>(); public IIterator CreateIterator() { return new ConcreteIterator(items); } public void AddItem(object item) { items.Add(item); } }
最新高级C#面试知识点
- 责任链模式 (Chain of Responsibility Pattern):
- 使用场景:当多个对象处理同一请求,但具体处理者在运行时可动态确定时,如日志记录器链中的消息传递。
public abstract class Handler { protected Handler successor; public void SetSuccessor(Handler successor) { this.successor = successor; } public abstract void HandleRequest(int request); } public class ConcreteHandlerA : Handler { public override void HandleRequest(int request) { if (request < 10) { Console.WriteLine($"Request {request} is handled by Handler A.--from jhrs.com "); } else if (successor != null) { successor.HandleRequest(request); } } } public class ConcreteHandlerB : Handler { public override void HandleRequest(int request) { if (request >= 10 && request < 20) { Console.WriteLine($"Request {request} is handled by Handler B.--from jhrs.com "); } else if (successor != null) { successor.HandleRequest(request); } } }
最新高级C#面试知识点
- 命令模式 (Command Pattern):
- 使用场景:当需要将请求封装成对象,以便可以在不同时间执行和排队请求,如遥控器中的按钮操作。
public interface ICommand { void Execute(); } public class Receiver { public void Action() { Console.WriteLine("Receiver is performing an action."); } } public class ConcreteCommand : ICommand { private Receiver receiver; public ConcreteCommand(Receiver receiver) { this.receiver = receiver; } public void Execute() { receiver.Action(); } } public class Invoker { private ICommand command; public void SetCommand(ICommand command) { this.command = command; } public void ExecuteCommand() { command.Execute(); } }
最新高级C#面试知识点
- 策略模式 (Strategy Pattern):
- 使用场景:当需要在运行时选择不同算法或行为时,如排序算法、支付方式的选择。
public interface IStrategy { void Execute(); } public class ConcreteStrategyA : IStrategy { public void Execute() { Console.WriteLine("Strategy A is executed.--from jhrs.com "); } } public class ConcreteStrategyB : IStrategy { public void Execute() { Console.WriteLine("Strategy B is executed.--from jhrs.com "); } } public class Context { private IStrategy strategy; public Context(IStrategy strategy) { this.strategy = strategy; } public void SetStrategy(IStrategy strategy) { this.strategy = strategy; } public void ExecuteStrategy() { strategy.Execute(); } }
最新高级C#面试知识点
这些是23种常见的设计模式的示例及其使用场景。根据具体的问题和需求,选择适当的设计模式以提高代码的可维护性、可扩展性和可理解性。
十四、Grpc(远程过程调用)
gRPC(Google Remote Procedure Call)是一种高性能、跨语言的远程过程调用框架,由Google开发并开源。它使用Protocol Buffers(ProtoBuf)作为接口描述语言,可以用于构建分布式系统,支持多种编程语言,其中包括C#。
下面是关于C#中使用 gRPC 的一些重要信息:
- Protocol Buffers(ProtoBuf):gRPC使用ProtoBuf来定义服务接口和消息格式。ProtoBuf是一种轻量级的二进制序列化格式,用于定义数据结构和消息。这使得消息在网络传输中更加高效。
- IDL(接口定义语言):gRPC使用ProtoBuf文件来定义服务接口和消息。开发者可以使用ProtoBuf文件来定义RPC方法、消息类型、以及服务的契约。
- 自动生成代码:gRPC提供了一个工具,可以根据ProtoBuf文件自动生成C#代码,包括服务接口、消息类型和客户端/服务器框架。
- 双向流支持:gRPC支持双向流,允许客户端和服务器之间双向传输数据,这对于实时通信和流式数据非常有用。
- 异步和流式处理:gRPC在C#中提供异步支持,可以轻松处理异步操作和流式数据传输。
- 安全性:gRPC支持各种安全性特性,包括SSL/TLS加密,认证和授权,以确保通信的安全性。
- 跨语言支持:由于 gRPC 的跨语言性质,您可以在不同的编程语言中使用相同的服务定义文件,从而实现不同语言之间的互操作性。
- 生态系统:gRPC拥有一个丰富的生态系统,包括各种开发工具、插件和库,以便于开发者构建和维护 gRPC 服务。
在C#中使用gRPC,您需要使用gRPC工具生成客户端和服务器代码,然后实现您的服务逻辑。您可以使用NuGet包管理器来获取gRPC相关的C#库和工具。一旦您的gRPC服务就绪,客户端可以通过生成的C#客户端代理与服务进行通信。
总之,gRPC是一种强大的远程过程调用框架,特别适用于构建高性能、跨语言的分布式系统,并且在C#中有着很好的支持。它提供了许多功能,包括异步、双向流和安全性,使其成为现代分布式应用程序开发的有力工具。
十五、SignalR(实时调用)
SignalR 是一个由 Microsoft 开发的开源库,用于实现实时网络应用程序的功能。它是专门设计用于创建实时、双向通信的 ASP.NET 应用程序,而 C# 是 SignalR 最常用的后端编程语言之一。
以下是有关 C# 中使用 SignalR 的一些重要信息:
- 实时通信:SignalR 使开发者能够在客户端和服务器之间建立实时通信,允许服务器向客户端推送数据,反之亦然。这对于创建聊天应用、实时协作工具、实时通知等应用程序非常有用。
- 跨平台支持:SignalR 提供了多种客户端库,支持不仅 C#,还包括 JavaScript、Java、Python 和其他语言。这意味着您可以在不同平台上创建支持实时通信的应用程序。
- Hub:SignalR 中的核心概念是 “Hub”,它是一个可从客户端调用的 C# 类,也可以从服务器端推送消息给客户端。Hub 提供了一种简单的方式来组织客户端和服务器之间的通信。
- 连接管理:SignalR 处理了连接管理,包括重新连接、断线检测和自动重新连接。这些特性有助于维护稳定的实时通信。
- 扩展性:SignalR 允许您轻松扩展功能,包括自定义传输、自定义中间件和集线器生命周期管理。这使您可以适应不同的应用需求。
- 集线器生命周期:SignalR 支持集线器生命周期管理,允许您在连接建立和断开时执行初始化和清理操作。这对于管理用户状态和资源非常有用。
- 安全性:SignalR 提供了内置的安全性特性,包括跨站请求伪造(CSRF)保护和身份验证/授权扩展点,以确保通信的安全性。
- 集成:SignalR 可以与 ASP.NET Core 和 ASP.NET Framework 集成,使其与现有的 Web 应用程序或服务无缝结合。
SignalR 适用于许多不同类型的应用程序,特别是需要实时通信的应用程序。无论是在线游戏、实时协作工具、在线投票应用还是实时监控系统,SignalR 都可以提供出色的实时通信能力。
总之,C# 的 SignalR 是一个强大的实时通信库,使开发者能够轻松创建实时网络应用程序,并支持跨平台开发。它的简单性和可扩展性使其成为构建实时应用程序的有力工具。