C# 性能优化最佳实践

简介: 1、显式注册的EvenHandler要显式注销以避免内存泄漏将一个成员方法注册到某个对象的事件会造成后者持有前者的引用。在事件注销之前,前者不会被垃圾回收。private void Form1_Load(){ …… //注册事件 CommandRemotingContext.CmdChanged += new ReciverCmdStateChangedEventHandl

1、显式注册的EvenHandler要显式注销以避免内存泄漏

将一个成员方法注册到某个对象的事件会造成后者持有前者的引用。在事件注销之前,前者不会被垃圾回收。

private void Form1_Load()
{
  ……
  //注册事件
  CommandRemotingContext.CmdChanged += new ReciverCmdStateChangedEventHandler(this.CommandRemotingContext_CmdChanged);
  ……
}
private void Form1_FromClosed()
{
  ……
  //关闭窗体时及时释放事件
  CommandRemotingContext.CmdChanged -= new ReciverCmdStateChangedEventHandler(this.CommandRemotingContext_CmdChanged);
  ……
}

由事件引起的内存泄漏问题:

  • 对象A订阅了对象B中的事件
  • 对象A的生命周期远远大于对象B
  • 对象A没有取消订阅对象B的时间
  • 最终导致对象B无法释放

2、控件绑定的数据源批量操作应避免自动刷新

  • 客户端批量操作数据时,控件自带的刷新操作,会造成不必要的时间消耗
  • 当数据源(如DataTable、Array、List、ObservableCollection或其他IListSource等)被绑定到控件时,批量操作数据时应该断开绑定或挂起控件的刷新。
    this.gcBillList.DataSource = null;
    DataRowCollection rows = this.ds.Tables[0].Rows;
    foreach (DataRow row in rows)
    {
        // DataRow数据操作
    }
    this.gcBillList.DataSource = this.ds.Tables[0].DefaultView;

3、减少客户端与服务端的通信次数

  • WebService调用并非越少越好,传输数据量较大的情况可考虑拆分为多次调用
  • 对于短WebService的调用,应尽量合并以减少交互次数
    //多次调用了相同的WS  
    txtCompanyName.Text=SCPubFunctionClient.PublicWSCal<string>(“ForTest”, “GetCompanyNameByID”,“0001”);  txtCompanyInnerName.Text=SCPubFunctionClient.PublicWSCal<string>(“ForTest”, “GetCompanyInnerNameByID”,“0001”);
    //合并相邻的WS  
    string[] result=SCPubFunctionClient.PublicWSCal<string>(“ForTest”, “GetCompanyNameAndInnerNameByID”,“0001”);
    txtCompanyName.Text=result[0];
    txtCompanyInnerName.Text= result[1];  

4、减少客户端与服务端的通信次数

如非必要,应尽量避免在循环体内重复调用WebService

//循环调用了相同的WS  
List<Person> persons;
……
foreach(string personID in personIDs)
{
	person=HRPubWsClient.getPerson(personID);
	persons.Add(person);
}
//合并WS  
List<Person> persons;
……
persons =HRPubWsClient.getPersonList(personIDs);
5、使用泛型来避免装箱、拆箱操作(减少垃圾回收压力)

  • 装箱操作会造成GC压力;如果发生在集合中,应该使用泛型集合避免。
  • 对于值类型的集合,使用List<T>来代替ArrayList,使用Dictionary<TKey, TValue> 来代替Hashtable。
    ArrayList h=new ArrayList();  //不建议
    h.Add(1);
     
    List<object> h = new List<object>();  //不建议
    h.Add(1);
     
    List<int> h = new List<int>();    //建议
    h.Add(1);
6、字符串操作:

 C# 字符串操作--减少垃圾回收压力
7、使用常量避免创建对象

  • 如下例,程序中存在大量 new decimal(0)的代码,这会导致小对象频繁创建及回收;正确的做法是使用 Decimal.Zero 常量。
    private string CurrencyCalc()
    {
        if (firstValue == new decimal(0))  ……
        if (secondValue == new decimal(0)) ……
        if (thirdValue == new decimal(0)) ……
        if (fourthValue == new decimal(0)) ……
        ……
    } 

8、避免不必要的抛出异常

C# 异常处理(Catch Throw)IL分析

9、使用RemoveAll而非RemoveAt进行删除多个元素

  • 使用RemoveAll方法对集合(如List)中的多个元素进行一次性删除时,只会对List的内部数组做一次resize 操作,效率明显高于循环调用RemoveAt。
    List<string>  lst = new List<string> {"1", "2", "3", "1", "2", "4"};
    //不建议:
    for (int i = lst.Count - 1; i >= 0; i--)
    {
        if (lst[i] == "1" || lst[i] == "2")
        {
            lst.RemoveAt(i);
         }
    }
     
    //建议:
    lst.RemoveAll(s => s == "1" || s == "2");

10、C# DataSet性能最佳实践

11、反射与动态绑定--减少CPU占用

  • 反射技术是将编译期间的静态绑定转换为延迟到运行期间的动态绑定。
  • C#主要支持 5 种动态创建对象的方式(时间消耗来自网络,与我实测的差距挺大,具体测试见下方):
动态创建对象的方式
与Direct Create
1.Type.InvokeMember
慢40倍以上
2.ContructorInfo.Invoke
慢40倍以上
3.Activator.CreateInstance(Type)
慢7倍
4.Activator.CreateInstance(assemblyName, typeName)
慢1000倍以上
5.Assembly.CreateInstance(typeName)
慢40倍以上
  • 应尽量避免使用反射和动态绑定;如必须使用,要遵循以下原则:
1. 使用接口调用方式将动态绑定改造为早期绑定(Direct Call)
2. 使用 Activator.CreateInstance(Type)方式动态创建对象

3. 使用typeof操作符代替GetType调用

小注:

通过循环创建实例记录时间如下:

加载程序集、获取类型在循环外部时间如下(这时不同创建方式消耗时间差距挺大):


代码如下:

 public void TestCreateInstance()
        {
            Stopwatch watch1 = new Stopwatch();
            var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
            Type type = asmb.GetType("ReflectiveClassLibrary.TestClass");
            int num = 100000;
            watch1.Start();

            for (int i = 0; i < num; i++)
            {
                //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
                //Type type = asmb.GetType("ReflectiveClassLibrary.TestClass");
                Activator.CreateInstance(type);
            }
            watch1.Stop();
            label1.Text = "Activator.CreateInstance(Type type)时间:" + watch1.ElapsedMilliseconds + "毫秒";

            watch1.Reset();
            watch1.Start();
            for (int i = 0; i < num; i++)
            {
                Activator.CreateInstance("ReflectiveClassLibrary", "ReflectiveClassLibrary.TestClass");
            }
            watch1.Stop();
            label2.Text = "Activator.CreateInstance(string assemblyName,string typeName)时间:" + watch1.ElapsedMilliseconds + "毫秒";

            watch1.Reset();
            watch1.Start();
            for (int i = 0; i < num; i++)
            {
                //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
                //加载程序集                
                asmb.CreateInstance("TestClass");
            }
            watch1.Stop();
            label3.Text = "assembly.CreateInstance(string typeName)时间:" + watch1.ElapsedMilliseconds + "毫秒";

            watch1.Reset();
            watch1.Start();
            for (int i = 0; i < num; i++)
            {
                //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
                //Type type = asmb.GetType("ReflectiveClassLibrary.TestClass");
                object obj = type.InvokeMember(null, BindingFlags.Public |
               BindingFlags.Instance | BindingFlags.CreateInstance, null, null, null);
            }
            watch1.Stop();
            label4.Text = "Type.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args)时间:" + watch1.ElapsedMilliseconds + "毫秒";

            watch1.Reset();
            watch1.Start();
            for (int i = 0; i < num; i++)
            {
                //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
                //Type type = asmb.GetType("ReflectiveClassLibrary.TestClass");
                ConstructorInfo constructorInfo = type.GetConstructors()[0];
                constructorInfo.Invoke(null);
            }
            watch1.Stop();
            label5.Text = "ContructorInfo.Invoke(object[] parameters)时间:" + watch1.ElapsedMilliseconds + "毫秒";

        }

加载程序集、获取类型在循环内部时间如下(这时不同创建方式消耗时间差距比较小)


代码如下:

 public void TestCreateInstance()
        {
            Stopwatch watch1 = new Stopwatch();
            //var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
            //Type type = asmb.GetType("ReflectiveClassLibrary.TestClass");
            int num = 100000;
            watch1.Start();

            for (int i = 0; i < num; i++)
            {
                var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
                Type type = asmb.GetType("ReflectiveClassLibrary.TestClass");
                Activator.CreateInstance(type);
            }
            watch1.Stop();
            label1.Text = "Activator.CreateInstance(Type type)时间:" + watch1.ElapsedMilliseconds + "毫秒";

            watch1.Reset();
            watch1.Start();
            for (int i = 0; i < num; i++)
            {
                Activator.CreateInstance("ReflectiveClassLibrary", "ReflectiveClassLibrary.TestClass");
            }
            watch1.Stop();
            label2.Text = "Activator.CreateInstance(string assemblyName,string typeName)时间:" + watch1.ElapsedMilliseconds + "毫秒";

            watch1.Reset();
            watch1.Start();
            for (int i = 0; i < num; i++)
            {
                var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
                //加载程序集                
                asmb.CreateInstance("TestClass");
            }
            watch1.Stop();
            label3.Text = "assembly.CreateInstance(string typeName)时间:" + watch1.ElapsedMilliseconds + "毫秒";

            watch1.Reset();
            watch1.Start();
            for (int i = 0; i < num; i++)
            {
                var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
                Type type = asmb.GetType("ReflectiveClassLibrary.TestClass");
                object obj = type.InvokeMember(null, BindingFlags.Public |
               BindingFlags.Instance | BindingFlags.CreateInstance, null, null, null);
            }
            watch1.Stop();
            label4.Text = "Type.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args)时间:" + watch1.ElapsedMilliseconds + "毫秒";

            watch1.Reset();
            watch1.Start();
            for (int i = 0; i < num; i++)
            {
                var asmb = Assembly.LoadFrom("ReflectiveClassLibrary.dll");
                Type type = asmb.GetType("ReflectiveClassLibrary.TestClass");
                ConstructorInfo constructorInfo = type.GetConstructors()[0];
                constructorInfo.Invoke(null);
            }
            watch1.Stop();
            label5.Text = "ContructorInfo.Invoke(object[] parameters)时间:" + watch1.ElapsedMilliseconds + "毫秒";

        }

测试代码如下: c# 反射测试demo

12、序列化与反序列化

  • 相对于XML、二进制序列化方式,Protobuf效率较高,支持数据量较大
  • protobuf序列化后的大小是json的1/10,xml格式的1/20,是二进制序列化的1/10

C#  序列化

C# Protobuf-Net 序列化

13、数据压缩

在数据从客户端传输到服务端过程,为减少数据传输量,建议对数据进行压缩处理。

       常用的数据压缩: C# 文件流压缩解压

14、


相关文章
|
4月前
|
人工智能 API 图形学
《Unity3D NavMeshAgent与Rigidbody移动同步问题的技术拆解》
本文聚焦Unity3D开放世界游戏开发中,NavMeshAgent与物理刚体协同移动的高频异常问题。项目基于Unity 2022.3.12f1 LTS与URP,在PC和PS5端出现斜坡打滑、碰撞障碍物路径死循环、高空坠落路径失效三类异常。通过分层排查,定位根源为组件控制权争夺、路径重算与障碍物移动不同步、NavMeshAgent内部物理冲击保护机制缺陷。进而从动态切换控制权、设计同步机制、修复状态恢复逻辑三方面提出解决方案。
238 0
|
11月前
|
人工智能 自然语言处理 开发工具
自建 DeepSeek 时代已来,联网搜索如何高效实现
随着 DeepSeek 等高质量开源大模型的涌现,企业自建智能问答系统的成本已降低 90% 以上。基于 7B/13B 参数量的模型在常规 GPU 服务器上即可获得商业级响应效果,配合 Higress 开源 AI 网关的增强能力,开发者可快速构建具备实时联网搜索能力的智能问答系统。
1053 100
|
2月前
|
JSON 监控 API
打造智能通知中心:利用n8n的HTTP Request节点聚合多平台消息
在信息碎片化时代,n8n助力高效整合多平台消息。本文教你利用其HTTP Request节点,聚合GitHub、天气、新闻等数据,构建智能通知中心,通过Slack统一推送,实现自动化信息管理,提升工作效率。
|
7月前
|
Java API 微服务
2025 年 Java 校招面试全攻略:从面试心得看 Java 岗位求职技巧
《2025年Java校招最新技术要点与实操指南》 本文梳理了2025年Java校招的核心技术栈,并提供了可直接运行的代码实例。重点技术包括: Java 17+新特性(Record类、Sealed类等) Spring Boot 3+WebFlux响应式编程 微服务架构与Spring Cloud组件 Docker容器化部署 Redis缓存集成 OpenAI API调用 通过实际代码演示了如何应用这些技术,如Java 17的Record类简化POJO、WebFlux构建响应式API、Docker容器化部署。
348 5
|
10月前
|
人工智能 搜索推荐 数据挖掘
从迷茫到自信:入职培训的5个关键
这篇文章不是空洞的理论堆砌,而是基于我在实际工作中的摸索与思考,结合中国大陆近两年的前沿实践,提炼出的一套实用方法论。我会从文化融入、产品认知、团队连接、技术赋能到政策落地五个维度展开,细化到每一个操作细节,同时分享一些真实案例,希望能为资深HR和培训负责人带来启发。
|
Apache
基于apache集合工具包的并集、交集、差集工具类
基于apache集合工具包的并集、交集、差集工具类
367 1
|
11月前
|
人工智能 安全 算法
《生成式AI牵手量子密码学,网络安全开启“超维”防护》
在数字时代,网络安全至关重要。传统防护手段逐渐失效,量子密码学与生成式AI的结合带来了新曙光。量子密码学基于量子力学原理,提供无条件安全的密钥分发;生成式AI则通过智能分析和模拟攻击,提升检测与防御效率。两者携手,优化密钥管理、加密算法及数据隐私保护,为网络安全带来全方位突破。学术界与产业界的共同努力将推动这一变革,构筑坚不可摧的安全防线。
475 4
|
缓存 并行计算 数据可视化
Matplotlib性能优化:提升图表渲染速度
【4月更文挑战第17天】提升 Matplotlib 渲染速度的技巧:1) 减少数据点;2) 使用矢量化操作;3) 减少图表元素;4) 增量渲染;5) 优化图像保存;6) 更换更快的后端;7) 并行处理;8) 避免循环内绘图;9) 利用缓存;10) 使用专业图形工具。注意根据具体需求调整优化策略。
1639 2
|
SQL 关系型数据库 MySQL
删库,误清数据怎么办?MySQL数据恢复指南
相信很多同学在面对线上数据库时都畏手畏脚,即使这样都难免手滑,一不小心手一抖就将数据或者是表,库删除。当然一些注重规范的公司,不会给开发人员删除表或者是库的权限,但误删数据是常有的事,那么这种情况发生,我们改怎么办呢?跑路?哈哈,当然删库跑路是句玩笑话,本文就为大家介绍一些数据误删除恢复的办法。
3927 0
EMQ
|
开发工具
MQTT 5.0 报文解析 04:PINGREQ 与 PINGRESP
除了用于连接、发布和订阅的控制报文,MQTT 还有一类报文用于在客户端和服务端之间模拟心跳,以达到保持连接的目的,它们分别是 PINGREQ 报文和 PINGRESP 报文,我们通常也会称它们为心跳报文。
EMQ
533 0
MQTT 5.0 报文解析 04:PINGREQ 与 PINGRESP

热门文章

最新文章