11个提高C#代码性能的技巧
作者:admin 时间:2023-4-28 16:20:18 浏览:为什么要关注代码性能?因为代码性能会影响程序的执行效率,在客户端就表现为等待时间的长短,这就影响到用户的使用体验,因此,我们开发人员不得不对代码性能给予足够的重视。本文中,我将总结11个提高C#代码性能的技巧。
1、循环并行化
foreach
是当今最常用的循环,仅次于 for
循环。它提供了灵活性,不用担心循环计数,它会运行到集合的长度。
在foreach
循环中,迭代在同一线程中一个接一个地执行,因此总执行时间将随集合大小线性增长。
使用 .Net Framework 4.0 提供给开发者的并行版本的foreach
循环,性能可以有很大的提升。
Parallel.ForEach
可用于实现 IEnumerable<T>
接口的任何集合,就像常规的 foreach
循环一样。Parallel.ForEach
将代替开发人员做很多工作:它根据机器上可用的核心将集合分成块,在单独的线程中调度和执行块,然后组合结果。
以下是 foreach
循环的并行版本与普通版本之间的性能比较:
如果集合很小并且单次迭代的执行时间很快,则将 foreach
切换为 Parallel
。Parallel.ForEach
可能使性能变差,因为它通过拆分和收集结果增加了管理循环的成本。
2、使用 StringBuilder 而不是字符串连接
在 C# 中,字符串连接在每次调用时都会创建一个新的字符串对象。如果你连接大量字符串,这可能效率低下。为避免这种情况,请改用 StringBuilder
。
例子:
var sb = new StringBuilder();
sb.Append("Hello ");
sb.Append("World");
var result = sb.ToString();
3、使用 LINQ 进行过滤和排序
在 C# 中,LINQ
提供了用于筛选、排序和操作集合的强大工具。LINQ
比手动遍历集合并对每个项目执行操作更有效。
例子:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var result = numbers.Where(n => n % 2 == 0).OrderByDescending(n => n);
4、一次性物化 LINQ 查询
何为一次性物化 LINQ
查询?即是不要使用表达式树来保存结果,而是尽可能通过执行来提取结果。
使用 IEnumerable
或 IQueryable
接口编写 LINQ
查询时,开发人员可以具体化(调用 ToList
或 ToArray
类似方法)或不立即具体化查询(延迟加载)。
如果查询不是通过调用 ToList/ToArray
方法实现的,多次迭代集合将影响应用程序的性能。
这是一个示例,我们在其中测试延迟加载 LINQ
查询与物化查询的性能。
[MemoryDiagnoser]
public class LinqTest {
[Benchmark]
public void NoMaterializedTest() {
var list = Enumerable.Range(0, 50000000);
var filteredlist = list.Where(element => element % 100000 == 0);
foreach(var element in filteredlist) {
//some logic here
}
foreach(var element in filteredlist) {
// some logic here
}
foreach(var element in filteredlist) {
// some logic here
}
}
[Benchmark]
public void MaterializedTest() {
var list = Enumerable.Range(0, 5000000);
var filteredlist = list.Where(element => element % 10000 == 0).ToList();
//ToList() method will execute the expression tree and get the result for future purpose.
foreach(var element in filteredlist) {
// some logic here
}
foreach(var element in filteredlist) {
// some logic here
}
foreach(var element in filteredlist) {
// some logic here
}
}
public static void Main() {
//Execute LinqTest class methods to check the benchmark
var result = BenchmarkRunner.Run < LinqTest > ();
}
在这里可以看到差异,与延迟加载 LINQ
查询相比,物化查询的性能提高了三倍。尽管这种方法并不总是有效,因为我们可能不会多次执行相同的结果集。在具体化 LINQ
查询之前始终检查大小写。
5、异步编程
异步编程是一种允许你的代码在等待长时间运行的操作完成时继续执行的技术。这在处理 I/O 操作时特别有用,例如读取和写入文件或发出 Web 请求。
在 C# 中,异步编程是使用 async
和 await
关键字实现的。
例子:
async Task<string> ReadFileAsync(string filePath)
{
using (var reader = new StreamReader(filePath))
{
return await reader.ReadToEndAsync();
}
}
6、对大型集合使用 IEnumerable 而不是列表
在 C# 中,列表是存储和操作对象集合的有效方式。但是,将 IEnumerable
用于广泛的集合会更有效,因为它避免了创建和维护 List
对象的开销。
例子:
public IEnumerable<int> GetNumbers()
{
for (int i = 0; i < 1000000; i++)
{
yield return i;
}
}
foreach (var number in GetNumbers())
{
Console.WriteLine(number);
}
7、使用枚举标志进行位运算
在 C# 中,枚举可以用作标志,这使您可以对它们执行按位运算。这在将多个值存储在单个变量中时非常有用。
例子:
[Flags]
public enum Colors
{
None = 0,
Red = 1,
Green = 2,
Blue = 4
}
var colors = Colors.Red | Colors.Green;
8、对小型数据结构使用结构而不是类
在 C# 中,结构是值类型,类是引用类型。结构分配在栈上,而类分配在堆上。对于小型数据结构,使用结构可以更有效,因为它避免了堆分配和垃圾收集的开销。
例子:
public struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
var p = new Point(10, 20);
9、使用泛型
泛型是 C# 中的一项强大功能,可让你创建可重用的代码。通过使用泛型类型参数定义类或方法,你可以创建类型安全、可重用的组件,该组件可以处理各种数据类型。这可以通过减少你需要编写的重复代码来帮助提高性能。
下面是使用泛型创建可重用排序方法的示例:
public static void Sort<T>(T[] array) where T : IComparable<T>
{
for (int i = 0; i < array.Length; i++)
{
for (int j = i + 1; j < array.Length; j++)
{
if (array[j].CompareTo(array[i]) < 0)
{
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
10、使用接口实现松散耦合
在C#中,接口为组件之间的松散耦合提供了一种强大的机制。通过定义服务或功能的接口,您可以确保组件可以轻松换出或更换,而不会影响系统的其余部分。这种方法可以通过最小化更改对整个系统的影响来提高代码性能。
例如,考虑以下定义用于访问数据的通用存储库的接口:
interface IRepository<T> {
void Save(T entity);
T Get(int id);
}
这个接口可以定义一个通用的数据访问层,可以很容易地替换或更新而不影响系统的其余部分。
11、尽可能使用 Struct 而不是 Class
大多数时候我看到开发人员创建类时没有对 Class
和 Struct
进行很好的比较。开发人员通常可能需要分配一个数组或 List<T>
在内存中存储数万个对象。这个任务可以使用类或结构来解决。
正如我们所看到的, ListOfClassObjectTest
和ListOfStructsObjectTest
之间的唯一区别,是第一个测试创建类的实例,而第二个测试创建结构的实例。
如下示例中,PointClass
和 PointStruct
的代码是相同的:
//Reference type. Stored on Heap.
public class PointClass {
public int X {
get;
set;
}
public int Y {
get;
set;
}
}
//Value type. Stored on Stack
public struct PointStruct {
public int X {
get;
set;
}
public int Y {
get;
set;
}
}
[MemoryDiagnoser]
public class ClassVsStruct {
[Benchmark]
public void ListOfClassObjectsTest() {
const int length = 1000000;
var items = new List < PointClass > (length);
for (int i = 0; i < length; i++) {
items.Add(new PointClass() {
X = i, Y = i
});
}
}
[Benchmark]
public void ListOfStructsObjectTest() {
const int length = 1000000;
var items = new List < PointStruct > (length);
for (int i = 0; i < length; i++) {
items.Add(new PointStruct() {
X = i, Y = i
});
}
}
}
可以看到,使用结构的代码比使用类的代码运行速度快 15 倍。
对于类,CLR 必须将一百万个对象分配给托管堆并将它们的引用存储回List<T>
集合。在结构的情况下,将有唯一的对象分配到托管堆中 ,它是List<T>
集合的实例。一百万个结构将嵌入到该单个集合实例中。
总结
以上11点希望对各位开发者提升代码质量有所帮助,通过这些开发技巧,开发人员可以创建执行速度更快、占用系统资源更少的应用程序。
- 站长推荐