[9/11]C#性能优化-基本代码技巧-每个细节都有示例代码
在C#开发中,性能优化是提升系统响应速度和资源利用率的关键环节。
当然,同样是所有程序的关键环节。
通过遵循下述建议,可以有效地减少不必要的对象创建,从而减轻GC的负担,提高应用程序的整体性能。记住,优化应该是有针对性的,只有在确定了性能瓶颈之后,才应该采取相应的措施。
这里描述一些应用场景下,可以提高性能的基本代码技巧。对处于关键路径的代码,进行这类的优化还是很有意义的。普通代码可以不做要求,但养成一种好的习惯也是有意义的。
可以把循环的判断条件用局部变量记录下来。局部变量往往被编译器优化为直接使用寄存器,相对于普通从堆或栈中分配的变量速度快。如果访问的是复杂计算属性 的话,提升效果将更明显。
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
,我们确保该方法仅被调用一次,之后的循环迭代将使用这个局部变量进行判断,这通常会更快。
对于.NET
中的集合类(例如List
),它们的Count
属性是特别优化过的。这意味着获取Count
属性的值通常是非常快速的操作,因为它只是返回内部维护的一个字段值,而不是每次都要重新计算。因此,在这种情况下,预先将Count
存储在一个局部变量中并不会带来显著的性能提升,而且可能会使代码变得稍微复杂一些。
尽管对Count
属性做这样的优化可能不会带来明显的性能改善,但在其他场景下,比如当循环条件依赖于一个复杂的表达式或方法调用时,这样做仍然是有益的。总的来说,优化策略应该基于实际性能测试和分析,而不应仅仅依靠直觉或假设。在某些情况下,过早的优化可能会导致代码可读性下降而没有带来足够的性能增益。
最后,请记住“不要过早优化”这一原则。首先保证代码的清晰和正确,然后只在证明有性能瓶颈的情况下才进行针对性的优化。这样可以在保证代码质量的同时,也保持开发效率。
拼装好之后再删除是很低效的写法。有些方法其循环长度在大部分情况下为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
而非直接使用+
运算符拼接字符串。
获取集合元素时,有时需要检查元素是否存在。通常的做法是先调用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
考虑如下示例,其中包含了两处类型转换:
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
检查的方法是最优选择。