.NET高频技术点(持续更新中)

发布于:2025-05-10 ⋅ 阅读:(15) ⋅ 点赞:(0)
1. .NET 框架概述
  • .NET 框架的发展历程
  • .NET Core 与 .NET Framework 的区别
  • .NET 5 及后续版本的统一平台
2. C# 语言特性
  • 异步编程(async/await)
  • LINQ(Language Integrated Query)
  • 泛型与集合
  • 委托与事件
  • 属性与索引器
3. ASP.NET Core
  • MVC 架构模式
  • Razor 页面与视图组件
  • 中间件与管道
  • 依赖注入与配置管理
  • Web API 开发与 Swagger 集成
4. Entity Framework Core
  • 数据模型与数据库上下文
  • 迁移与数据库更新
  • LINQ 查询与性能优化
  • 事务管理与并发控制
  • 数据库提供程序与连接字符串配置
5. 微服务架构
  • 微服务的基本概念与优势
  • 使用 ASP.NET Core 构建微服务
  • 服务发现与负载均衡
  • API 网关与 Ocelot
  • 分布式缓存与消息队列
6. 容器化与 DevOps
  • Docker 容器化 .NET 应用
  • Kubernetes 集群管理与部署
  • CI/CD 管道与 Azure DevOps
  • 监控与日志管理
  • 自动化测试与部署
7. 安全性与身份验证
  • JWT(JSON Web Token)与 OAuth2.0
  • ASP.NET Core 中的身份验证与授权
  • 跨站脚本攻击(XSS)与跨站请求伪造(CSRF)防护
  • 数据加密与安全存储
  • HTTPS 与证书管理
8. 性能优化与调试
  • 性能分析与诊断工具
  • 内存管理与垃圾回收
  • 异步编程与线程池优化
  • 缓存策略与分布式缓存
  • 数据库查询优化与索引
9. 跨平台开发
  • .NET 跨平台支持与 Xamarin
  • MAUI(.NET Multi-platform App UI)
  • Blazor 与 WebAssembly
  • 跨平台桌面应用开发
  • 移动应用开发与发布

一、 值类型与引用类型的区别

    /// <summary>
    /// 类
    /// </summary>
    class TestClass
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    /// <summary>
    /// 结构体
    /// </summary>
    struct TestStruct
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //测试引用类型
            TestClass c1 = new TestClass { Id = 0, Name = "未定义" };
            TestClass c2 = c1;//c1给了c2
            c2.Id = 1;c2.Name = "a";
            Console.WriteLine($"c1[{c1.Id},{c1.Name}]");
            //测试值类型
            TestStruct s1 = new TestStruct { Id = 0, Name = "未定义" };
            TestStruct s2 = s1;//s1给了s2
            s2.Id = 2; s2.Name = "b";
            Console.WriteLine($"s1[{s1.Id},{s1.Name}]");
            Console.ReadKey();
        }
    }

在这里插入图片描述
值类型的每一次赋值都会执行一次逐字段的复制,引用类型的赋值只是指针的传递,其实也是生成新的指针实例。

二、 什么是装箱、拆箱

装箱:装箱操作时将值类型隐式地转换成引用类型。装箱一个数值会为其分配一个对象实例,并把该数值复制到新对象中。

拆箱:拆箱操作是指显式地把引用类型转换成值类型

int i=123;
object o=i;//装箱
int j=(int)o//拆箱

Int32—>Object 装箱 object o=i;//装箱
在这里插入图片描述
从图可知,对象 o 存的是地址引用,指向的是堆上的值,这个值的类型和变量 i 一样,也是 int 类型,值(123)也就是从变量 i Copy 过来的一个副本值而已。

Object —> Int32 拆箱 int a2=(int) objs//拆箱
在这里插入图片描述
要在运行时成功拆箱值类型,被拆箱的项必须是对一个对象的引用,该对象是先前通过装箱该值类型的实例创建的。

注意:频繁的装箱和拆箱比较耗费CUP资源,降低代码的执行效率和用户体验度。
改进方案:使用泛型类,泛型能很好的解决由装箱和拆箱带来的效率问题。具体使用请查阅泛型相关资料。

//简单案例
public T ReturnElement<T>() where T : ICollection<int>, new() 
{ 
    return new T(); 
}

三、 多线程下C#如何保证线程安全

问题出现的结果完全是无法确定的,包括但不限于如下结果:

  • 应用异常,且无法自恢复,必须重启站点或服务;
  • 陷入死循环,导致CPU占用100%,从而整台服务器崩溃;
  • 错误数据入库,导致一系列的排查、数据修复的困难,甚至可能无法修复数据;

因此,很有必要做几次全局的筛查,做一些特征值搜索和处理,
简单梳理了一下,凡是符合如下5种特征的代码,都存在线程不安全的可能性:

1、类的静态变量:
public class Iamclass
{
    static Dictionary<string, string> _cache = new Dictionary<string, string>();
    public static void Operation()
    {
        _cache.Add(new Guid().ToString(), "1");// 线程不安全代码
    }
}

2、类的静态属性:
public class Iamclass
{
    static Dictionary<string, string> Cache {get; set;} = new Dictionary<string, string>();
    public static void Operation()
    {
        Cache.Add(new Guid().ToString(), "1");// 线程不安全代码
    }
}

3、单例对象的静态变量:
public class XxxService
{
    IIamclass instance = IocHelper.GetSingleInstance<IIamclass>(); // 获取单例
}
public class Iamclass : IIamclass
{
    Dictionary<string, string> _cache = new Dictionary<string, string>();
    public void Operation()
    {
        _cache.Add(new Guid().ToString(), "1");// 线程不安全代码
    }
}

4、单例对象的静态属性:
public class XxxService
{
    IIamclass instance = IocHelper.GetSingleInstance<IIamclass>(); // 获取单例
}
public class Iamclass : IIamclass
{
    Dictionary<string, string> Cache {get; set;} = new Dictionary<string, string>();
    public void Operation()
    {
        Cache.Add(new Guid().ToString(), "1");// 线程不安全代码
    }
}

5、多线程共享的局部变量
public class XxxService
{
    public void Operation()
    {
        var cache = new Dictionary<string, string>();
        System.Threading.Tasks.Parallel.For(1, 10, idx =>
        {
            cache.Add(new Guid().ToString(), "1"); //线程不安全代码
        });
    }
}

列举下线程安全问题:
1、应用错误且无法恢复的,通常异常为:索引超出了数组界限:

public class MessageService : BaseService
{
    private static Dictionary<string, Timer> _timerDict = new Dictionary<string, Timer>();
    public async void SendMessageAsync(string msgId, MessageInputDto2 input)
    {
        var timer = new Timer(60 * 1000) { AutoReset = true };
        _timerDict[msgId] = timer;     // 问题代码
        timer.Elapsed += (sender, eventArgs) =>
        {
            try
            {
                /* 具体业务代码 */
                timer.Stop();
                timer.Close();
                _timerDict.Remove(msgId);
            }
            catch(Exception exp)
            {
                // 异常处理代码
            }
        }
    }
}

解决方法,一般是加锁
注意:如果加lock 可能出现瓶颈,要进行流程梳理,是否要更换实现方案:

lock(_timerDict)
{
    _timerDict[msgId] = timer;     // 问题代码
}
timer.Elapsed += (sender, eventArgs) =>
{
    try
    {
        /* 具体业务代码 */
        timer.Stop();
        timer.Close();
        lock(_timerDict)
        {
            _timerDict.Remove(msgId);
        }
    }
    catch(Exception exp)
    {
        // 异常处理代码
    }
}

2、陷入死循环,导致服务器CPU 100%卡顿问题:
有个常见业务,获取一串没有使用过的随机数或随机字符串,比如用户身份Token,比如抽奖等等
下面是常见的获取不重复的随机数代码,
在_rnd.Next 没有加锁,其内部方法InternalSample会导致返回结果都是0,从而导致while陷入死循环:

public class CodeService
{
    private static Random _rnd = new Random(Guid.NewGuid().GetHashCode());
    public static GetCode()
    {
        var ret = "";
        var redis = IocHelper.GetSingleInstance<IRedis>();
        // 获取一个未使用过的序号
        do
        {
            ret = _rnd.Next(10000).ToString();  // 问题代码
        }while(!redis.Add(ret, "", TimeSpan.FromSeconds(3600)));
        return ret;
    }
}

解决方法,双重校验:加锁,并判断循环次数:

public class CodeService
{
    private static Random _rnd = new Random(Guid.NewGuid().GetHashCode());
    public static GetCode()
    {
        var ret = "";
        var redis = IocHelper.GetSingleInstance<IRedis>();
        var maxLoop = 10;
        // 获取一个未使用过的序号
        do
        {
            lock(_rnd)
            {
                ret = _rnd.Next(10000).ToString();
            }
        }while(!redis.Add(ret, "", TimeSpan.FromSeconds(3600)) && (maxLoop--) > 0);
        if(maxLoop <= 0)
        {
            throw new Exception("10次循环,未找到可用数据:" + ret);
        }
        return ret;
    }
}

四、 内存溢出与内存泄露

1、内存溢出
系统不能再给你的请求分配所需要的空间了,比如你申请了30M,系统剩余内存只有20M了。这就叫内存溢出。
比如一个办公室空间有限只有5个工位,领导安排6个人来这屋,还有一个人怎么办?只能找领导安排其它地方了。还比如在栈的操作中如果栈已经满了,当我们再对栈进行入栈操作就会造成上溢。

2、内存泄露
内存泄露是申请了内存空间的变量一直在占用,无法释放。比如申请了一块内存空间,没有回收一直占用,直到最后内存溢出。
比如在C#中使用非托管代码,如果不使用析构函数回收就会造成内存泄露。如果不是特殊情况,所以建议尽量不要使用非托管资源来编写代码。还比如在代码中使用了静态变量也容易导致内存泄露,关于内存泄露的情况大家可以查看“避坑指南:可能会导致.NET内存泄露的8行为”

五、 讲讲.NET的GC原理

GC与内存管理(含深度解析)

六、 async/await相关问题

七、 事件和委托的异同

八、 依赖注入相关问题

九、 ASP.NET Core 中的服务生命周期

十、 ASP.NET Core中间件


网站公告

今日签到

点亮在社区的每一天
去签到