中级.NET开发工程师面试经历

前言

不幸进入了一家不守法,不遵守劳动合同的公司。新年老板开始短期优化员工,让员工无偿996加班一段时间(属于违法行为,猜测是优化员工筛选帕鲁)。跟管理层反映无果后,就有了跳槽的打算,于是开始寻找新的机会,参加了一家中级.NET开发工程师的面试。

面试题目(只记录了还记得的部分)

一.简单说下.NETCORE的生命周期?

1.文言一心版回答:
.NET Core应用程序的生命周期通常从创建应用程序的主入口点开始,随后是初始化应用程序,配置服务,建立请求管道,以处理每个HTTP请求。在请求处理完毕后,应用程序会关闭。

2.ChatAI3.5版回答:
在.NET Core 应用程序中,有以下几个主要的生命周期阶段:
a.启动阶段(Bootstrap):在这个阶段,应用程序会进行一些初始化操作,比如加载配置,注册服务,设置日志等。
b.运行时阶段(Runtime):在这个阶段,应用程序正式开始执行业务逻辑,处理请求等操作。
c.关闭阶段(Shutdown):在应用程序即将停止运行时,会执行一些清理操作,释放资源等。
在这些阶段中,可以通过事件来监听应用程序的状态变化,以便在合适的时机执行相应的操作。比如可以使用以下事件:
ApplicationStarted:应用程序启动时触发。
ApplicationStopping:应用程序即将停止时触发。
ApplicationStopped:应用程序已经停止时触发。
通过监听这些事件,可以在不同阶段执行相应的代码,以实现更精细的控制和管理应用程序的生命周期。

3.博客园某博主的相关回答:
.NetCore 依赖注入的三个生命周期理解:
AddTransient瞬时模式:每次请求,都获取一个新的实例。即使同一个请求获取多次也会是不同的实例(注意这里的获取多次对象是指通过上下文获取对象,而不是共用上一个对象)
AddScoped:每次请求都获取一个新的实例。同一个请求获取多次会得到相同的实例。
AddSingleton单例模式:每次都获取同一个实例。

二.C#如何保证在并发情况下接口不会被重复触发?

1.接口在都是同步代码的情况下,我会使用lock语句块对代码进行加锁。

2.接口在存在异步代码的情况下,我会使用Semaphore或者SemaphoreSlim,通过信息量控制接口线程保证接口不会被重复触发。

3.通过redis锁的方式,保证单据的唯一性。

4.通过数据库锁的方式,使用WITH(TABLOCK,HOLDLOCK)选项来获取表级别的排它锁,或者使用WITH(UPDLOCKL,ROWLOCK)选项来获取行级别的排它锁,从而控制对数据库对象的并发访问。

三.引用类型和值类型有什么区别?

1.引用类型:
a.变量存储对象的引用(内存地址),实际数据存储在堆上。
b.引用类型的变量指向实际的对象,对变量赋值只复制引用,不复制整个对象(比如说a对象赋值给b对象,当我们对b对象进行更改的时候,a对象也会跟着改变,因为他们实际上是指向同一个内存地址,值从始至终都只有一个)
c.常用的引用类型有:类(Class),接口(Interface),委托(Delegate),字符串(string),数组(Array)等

2.值类型:
a.变量直接包含数据,存储在栈中
b.值类型赋值会直接复制一个新的数据(比如说a赋值给b,就会在b那复制一份新的数据,对b进行任何操作都不影响a)
c.常用的值类型有:整数类型(int,long,short,byte),浮点数类型(float,double,decimal),字符类型(char),布尔类型(bool),结构体(struct),枚举(enum),int?,DateTime?(问号表示值可以为null)等

四.那怎样能让引用类型和值类型一样,在赋值的时候创建新的数据?(在更改新的引用类型对象b的时候,不影响原来的引用类型对象a)

ChatAI3.5回答:
1.深拷贝(Deep Copy):对于自定义的引用类型,可以在对象内部实现深拷贝逻辑;即创建一个新的对象,将原始对象的数据赋值到新对象中。这样,在对新的对象进行修改时不会影响初始对象。

2.使用特定的复制方法:对于一些标准的引用类型,比如说字符串和数组,就可以使用string.Copy()和Array.Copy()方法来创建新的对象。

个人补充:
1.C#在方法调用参数的时候,参数类型前面可以加ref或者in关键字;
其中ref关键字会让任何传递过来的参数当作引用类型处理(即在方法中变更参数,传入方法中的值参数也会被当作引用类型处理,同步发生改变)
而in关键,会将传递过来的参数视为只读,这样就避免了在方法内部对参数进行修改。

2.C#可以通过序列化,反序列化生成一个新的引用类型,不影响初始引用对象。

五.C#获取接口响应时间。

ChatAI3.5回答:
1.使用Stopwatch对象,使用stopwatch.Start()方法和stopwatch.Stop()方法分别置于接口请求前后,而后通过stopwatch.Elapsed属性获取接口响应时间。

2.接口请求前后分别使用DateTimeOffset.UtcNow.ToUnixTimeSeconds()获取当前服务器的时间戳值,通过两个时间戳的差值得到接口响应时间。

六.需要所有接口都这样写一次吗?可以统一获取接口响应时间吗?

ChatAI3.5回答:
1.在C#中,可以通过创建一个统一的HTTP客户端类来封装发送HTTP请求的逻辑,并在其中实现统一计算接口请求消耗时间的功能。这样可以避免每个接口单独计算请求消耗时间的重复操作。
代码案例如下:

using System;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;

public class CustomHttpClient
{
     private HttpClient _httpClient;
     
     public CustomHttpClient(){
      	_httpClient = new HttpClient();
	 }
	 
	public async Task<string> GetResponseTimeAsync(string url){
		var stopwatch = new Stopwatch();
		stopwatch.Start()
		HttpResponseMessage response = await _httpClient.GetAsync(url);
		stopwatch.Stop();
	 	TimeSpan elapsed = stopwatch.Elapsed;
	 	return $"接口响应时间为:{elapsed.TotalMilliseconds}ms";
	}
}

七.接口的异常捕捉。

ChatAI3.5回答:
在C#中,你可以通过自定义Action Filter来实现用户请求接口异常时记录异常信息,请求参数等功能。Action Filter是ASP.NET Core中一种常用的过滤器,可以用来在执行控制器中的Action前后执行一些逻辑,列入日志记录,异常处理等操作。

下面是一种简单的示例,演示了如何在ASP.NET Core 中实现一个自定义Action Filter来记录异常信息和请求参数:

首先,定义一个自定的Action Filter类,继承IActionFilter接口:

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

public class CustomActionFilter : IActionFilter{
	
	private readonly ILogger<CustomActionFilter> _logger;
	
	public CustomActionFilter(ILogger<CustomActionFilter> logger){
		_logger = logger;
	}

	public void OnActionExecting(ActionExecutingContext context){
		//在执行Action前记录请求参数
		var parameters = context.ActionArguments;
		_logger.LogInformation("Request Parameters:{@parmeters}",parameters);
	}

	public void OnActionExecuted(ActionExecutedContext context){
		//在执行Action后记录异常信息
		if(context.Exception != null){
			_logger.logError(context.Exception,"An error occurred while processing the request.");
		}
	}
}

然后,在Starup.cs中注册该Aciton Filter;

using Microsoft.Extensions.DependencyInjection;

public class Startup{
	
	public void ConfigureServices(IServericeCollection services){
		services.AddControllers(options => {
			option.Filters.Add<CustomActionFilter>();
		});
		//添加其他服务配置
	}
}

在上述案例中,我们定义了一个 CustomActionFilter类来实现自定一个的Action Filter功能,其中在OnActionExecuting方法中记录请求参数,在OnActionExecuted方法中记录异常信息。然后在Startup.cs中通过options.Filters.Add()注册该Action Filter。

通过这种方式,当用户请求接口发生异常,Action Filter会自动捕捉并记录异常信息,请求参数等,方便后续排查问题和分析。

八…NETCORE注册如何注册并使用Session?

ChatAI3.5回答:
在Asp.NET Core中,你可以通过AddSession方法来注册并配置Session功能。下面是一个简单的示例。

首先,在Startup.cs的ConfigureServices方法中进行注册和配置:

public class Startup{
	public void ConfigureServices(IServiceCollection services){
		//其他服务配置
		services.AddSession(options=>{
			options.IdleTImeout = TimeSpan.FromMinutes(20);
			options.Cookie.HttpOnly = true;
			options.Cooie.IsEssential = true;
		});
		//其他服务配置
	}
	
	public void Configure(IApplicationBuilder app,IWebHostEnvironment env){
		//其他中间件配置

		app.UseSession();
		
		//其他中间件配置
	}
}

在上述案例中,我们功过services.AddSession()方法注册了Session功能,并且配置了Session的超时时间,Cookie特性等。然后在Configure方法中使用app.UseSession()启用了Session中间件。
接下来,可以在控制器或者Razor页面中使用HttpContext.Session属性来访问和操作Session数据,例如:

public	class HomeController:Controller{
	public IActionResult Index(){
		HttpContext.Session.SetString("UserName","Joker");
		var userName = HttpContext.Session.GetString("UserName");
		return View();
	}
}

上述案例中,通过HttpContext.Session.SetString()方法存储Session值,HttpContext.Session.GetString()方法获取对应的Session值

九.数据库如何实现事务隔离?

数据库可以通过加锁来实现事务隔离,常见的锁有行级锁,表级锁,页级锁等。这些锁可以用于实现不同的事务隔离级别,例如可重复读和串行化级别。比如:

1.行级锁:行级锁是对数据库中的行进行加锁,当一个事务要访问某一行数据时,会获取该行的锁,其他事务无法修改该行知道锁被释放。行及锁可以实现较高的并发性和灵活性;sqlserver常见的行级锁有:共享锁(Shared Lock),排他锁(Exclusive Lock)

2.表级锁:表级锁时对整个表进行加锁,当一个事务要访问表中的数据时,会获取整个表的锁,其他事务无法修改表中的数据知道锁被释放。表级锁通常用于较低级别的隔离要求,因为它限制了其他事务对表的访问;sqlserver常见的表级锁有:表共享锁(table shared lock),表排它锁(table exclusive lock)

3.页级锁:页级锁时对数据库中的页(通常是连续的数据块)进行加锁,当一个事务需要访问某一页数据时,会获取该页的锁,其他事务无法修改该页中的数据知道锁被释放。页级锁介于行级锁和表级锁之间,可以在一定程度上平衡并发性能和隔离级别要求;sqlserver常见的页级锁有:页共享锁(page shared lock),页排它锁(page exclusive lock)

4.sqlserver还提供了其他类型的锁,比如 意向锁(intent lock),更新锁(update lock),行版本锁(row versioning locks)等,用于支持不同的并发控制需求

十.聚集索引和非聚集索引。

sqlserver中的聚集索引和非聚集索引是数据库中常用的两种索引类型,他们在数据库查询性能优化和数据存储方面起到重要作用。
1.聚集索引(Clustered Index):
a. 定义:聚集索引确定了表中数据的物理排序顺序,并且叶子节点包含实际的数据行。
b.特点:每张表只有一个聚集索引,它决定了表中数据的物理存储顺序,因此表中的数据按照聚集索引的顺序进行存储。
c.优势:通过聚集索引可以快速查询范围内的数据,因为数据行在物理上相邻,适合范围查询和排序。
d.适用场景:适用于经常按照某列进行范围查找和排序的字段。

2.非聚集索引(Non-Clustered Index):
a.定义:非聚集索引中叶子节点并不包含实际的数据行,而是包含指向实际数据行的指针。
b.特点:一张表可以拥有多个非聚集索引,他们并不影响表中数据的物理存储顺序,而是在索引中维护指向实际数据行的引用。
c.优势:通过非聚集索引可以快速定位到需要的记录,适合于单值查询和覆盖索引。
d.适用场景:适合于经常要进行单值查询的字段,例如where子句中的条件字段。

总结

总的来说,这是一场很失败的面试经历,吃一堑长一智,再接再厉。

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-02-23 09:22:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-23 09:22:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-23 09:22:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-23 09:22:04       18 阅读

热门阅读

  1. MySQL

    2024-02-23 09:22:04       23 阅读
  2. 【npm install报错,如何解决记录】讲解

    2024-02-23 09:22:04       32 阅读
  3. 汽车会撞死人,应不应该限制汽车?

    2024-02-23 09:22:04       29 阅读
  4. lua 拓展math库,增加四舍五入函数 math.round

    2024-02-23 09:22:04       28 阅读
  5. Docker的优势及实际应用

    2024-02-23 09:22:04       23 阅读
  6. LeetCode //C - 901. Online Stock Span

    2024-02-23 09:22:04       31 阅读