[9/11]C#性能优化-基本代码技巧-每个细节都有示例代码

[9/11]C#性能优化-基本代码技巧-每个细节都有示例代码

在这里插入图片描述

前言

在C#开发中,性能优化是提升系统响应速度和资源利用率的关键环节。
当然,同样是所有程序的关键环节。
通过遵循下述建议,可以有效地减少不必要的对象创建,从而减轻GC的负担,提高应用程序的整体性能。记住,优化应该是有针对性的,只有在确定了性能瓶颈之后,才应该采取相应的措施。

9.基本代码技巧

这里描述一些应用场景下,可以提高性能的基本代码技巧。对处于关键路径的代码,进行这类的优化还是很有意义的。普通代码可以不做要求,但养成一种好的习惯也是有意义的。

9.1. 循环写法

可以把循环的判断条件用局部变量记录下来。局部变量往往被编译器优化为直接使用寄存器,相对于普通从堆或栈中分配的变量速度快。如果访问的是复杂计算属性 的话,提升效果将更明显。
for (int i = 0, j = collection.GetIndexOf(item); i < j; i++)

需要说明的是:这种写法对于CLR集合类的Count属性没有意义,原因是编译器已经按这种方式做了特别的优化。

这是一种常见的性能优化技巧,尤其是在循环中涉及到复杂计算或者访问相对耗时的操作(如属性访问、方法调用等)时。通过将这些操作的结果存储在一个局部变量中,可以避免在每次循环迭代时重复执行这些计算或访问,从而提高程序的执行效率。

示例解释

for (int i = 0, j = collection.GetIndexOf(item); i < j; i++)

这里,collection.GetIndexOf(item)可能是一个相对较重的操作,它需要遍历集合以找到特定项的位置。如果直接在循环条件中使用collection.GetIndexOf(item),那么这个方法将在每次循环迭代时被调用,导致性能下降。通过将其结果赋值给一个局部变量j,我们确保该方法仅被调用一次,之后的循环迭代将使用这个局部变量进行判断,这通常会更快。

关于CLR集合类的Count属性

对于.NET中的集合类(例如List),它们的Count属性是特别优化过的。这意味着获取Count属性的值通常是非常快速的操作,因为它只是返回内部维护的一个字段值,而不是每次都要重新计算。因此,在这种情况下,预先将Count存储在一个局部变量中并不会带来显著的性能提升,而且可能会使代码变得稍微复杂一些。

实际应用建议

尽管对Count属性做这样的优化可能不会带来明显的性能改善,但在其他场景下,比如当循环条件依赖于一个复杂的表达式或方法调用时,这样做仍然是有益的。总的来说,优化策略应该基于实际性能测试和分析,而不应仅仅依靠直觉或假设。在某些情况下,过早的优化可能会导致代码可读性下降而没有带来足够的性能增益。

最后,请记住“不要过早优化”这一原则。首先保证代码的清晰和正确,然后只在证明有性能瓶颈的情况下才进行针对性的优化。这样可以在保证代码质量的同时,也保持开发效率。

9.2. 拼装字符串

拼装好之后再删除是很低效的写法。有些方法其循环长度在大部分情况下为1,这种写法的低效就更为明显了:

public static string ToString(MetadataKey entityKey)
{
    string str = "" ;
    object [] vals = entityKey.values;
    for ( int i = 0 ; i < vals.Length; i ++ )
    {
        str += " , " + vals[i].ToString();
    }
    return str == "" ? "" : str.Remove( 0, 1 );
}

推荐下面的写法:

if (str.Length == 0 )
    str = vals[i].ToString();
else
    str += " , " + vals[i].ToString();

其实这种写法非常自然,而且效率很高,完全不需要用个Remove方法绕来绕去。

推荐的改进方法

推荐的写法通过条件判断来避免不必要的字符串拼接,但这种方法仍然不是最优解,特别是在处理多个字符串拼接时。更高效的解决方案是使用StringBuilder类,它专门用于解决频繁修改字符串的情况。

使用StringBuilder的示例

这里提供一个基于StringBuilder的优化版本:

public static string ToString(MetadataKey entityKey)
{
    StringBuilder strBuilder = new StringBuilder();
    object[] vals = entityKey.values;
    for (int i = 0; i < vals.Length; i++)
    {
        if (i > 0) // 在非首次添加前加上", "
            strBuilder.Append(", ");
        strBuilder.Append(vals[i].ToString());
    }
    return strBuilder.ToString();
}

StringBuilder的优势

  • 高效性StringBuilder减少了因字符串拼接造成的临时对象创建,降低了内存使用和垃圾回收的压力。
  • 灵活性:可以方便地追加、插入、移除字符或子串,以及执行其他字符串操作。

这种方法不仅提高了效率,还使得代码更加清晰易读。因此,在需要频繁修改字符串的场景下,推荐使用StringBuilder而非直接使用+运算符拼接字符串。

9.3. 避免两次检索集合元素

获取集合元素时,有时需要检查元素是否存在。通常的做法是先调用ContainsKey(或Contains)方法,然后再获取集合元素。这种写法非常符合逻辑。

但如果考虑效率,可以先直接获取对象,然后判断对象是否为null来确定元素是否存在。对于Hashtable,这可以节省一次GetHashCode调用和n次Equals比较。

如下面的示例:

public IData GetItemByID(Guid id)
{
    IData data1 = null ;
    if ( this .idTable.ContainsKey(id.ToString())
    {
    data1 = this .idTable[id.ToString()] as IData;
    }
    return data1;
}

其实完全可用一行代码完成:

return this.idTable[id] as IData

9.4. 避免两次类型转换

考虑如下示例,其中包含了两处类型转换:

if (obj is SomeType)
{
    SomeType st = (SomeType)obj;
    st.SomeTypeMethod();
}

效率更高的做法如下:

SomeType st = obj as SomeType;
if (st != null )
{
    st.SomeTypeMethod();
}

as运算符的优势

  • 单次检查:避免了重复的类型检查,提升了性能。
  • 安全性:如果转换不可能成功(例如,当对象为null或其实际类型不匹配时),as运算符不会抛出异常,而是简单地返回null
  • 清晰性:代码更加直观,易于理解。

注意事项

尽管as运算符提供了更高的效率和更好的代码清晰度,但它仅适用于引用类型和可以为null的类型(如支持null的值类型)。对于不可为null的值类型(例如,基本数据类型int, float等),必须使用显式的类型转换或is关键字结合转换,因为这些类型不能被赋值为null。在这种情况下,使用is关键字加上类型转换是合适的。然而,在您的示例中,由于涉及到的是引用类型SomeType,因此as结合null检查的方法是最优选择。

你可能感兴趣的:(c#,c#,性能优化,android)