在这个版本中:Exists() over Any()、冻结的集合、块、专用的 Lock 类型和 Required 关键字。
我们每个人的发展方式都不同,这很好。但是我们都有一些其他人不知道的提示或技巧。在这篇文章中,我想和你分享我的前5个C#和.NET技巧和窍门。也许有些是熟悉的,也许是已知的,或者有些不适用于您。
提示和技巧的想法并不是要详细地深入它们,而是给你一个概念的小描述和一个例子。如果您对特定提示/技巧有任何疑问,请在评论中告诉我。如果对该主题有足够的要求,我将用一整篇文章来讨论它。
1:使用 Exists 而不是 Any
(.NET Framework 2.0)
我们大多数人都知道 Any() 是一个众所周知的 LINQ 语句。Any() 适用于任何 IEnumerable<T>,例如 List<T>、Array 和更多集合类型。一个简单的用法:
string hasProductsBeingOrderedAny = ProductList.Products.Any(x => x.Status == Status.Ordered)
? "There are products being ordered."
: "There are no products being orderd right now.";
Console.WriteLine(hasProductsBeingOrderedAny);
使用简单的 Any(),我检查是否有状态为 “ordered” 的产品。Any() 返回 true 或 false,具体取决于条件以及条件是导致 true 还是 false。
这就是 Exsits() 的发音方式:
string hasProductsBeingOrderedExists = ProductList.Products.Exists(x => x.Status == Status.Ordered)
? "There are products being ordered."
: "There are no products being orderd right now.";
Console.WriteLine(hasProductsBeingOrderedExists);
它看起来一样,除了使用了 Exists() 之外。它还返回 true 或 false,并且具有与 Any() 相同的条件。
为什么我们应该使用 Exists() 而不是 Any()?为什么 Exists() 存在?有几个原因:
Exists() 更快。
Exists() 效率更高。
Exists() 不需要创建枚举器。Any() 确实需要创建一个枚举器。
Exists() 更容易理解。刚接触 C# 的人会更好地理解它。
Exists() 不需要 LINQ,因为它直接构建在 System.Collections.Generic 之上。
我对这两段代码进行了基准测试,结果如下所示:
同意,差异看起来很小,但它确实表明 Exists() 更快。如果你有一个大型测试,有很多数据,你会看到 Exists() 会快得多。
2:冻结的集合
(.NET 8)
.NET 中的冻结集合是特殊类型的集合。列表、字典、数组等集合。设置数据后,无法更改冻结的集合。我们称之为 “不可变”。这意味着您可以查看里面的数据,但无法更改冻结后的数据。
它们的速度非常快,因为您的应用程序知道数据不会更改。正因为如此,它可以防止意外修改,使您的应用程序执行您不希望它做的事情。
以下是您通常将项目添加到集合的方法:
List<Product> products = ProductList.Products;
products.Add(new Product() {
Id = 101,
Available = true,
Status = Status.Delivered,
Stock = 100,
Title = "Testing"
});
foreach (Product product in products.Where(x => x.Stock > 10))
Console.WriteLine($"{product.Title} ({product.Stock})");
让我们采用相同的列表,但这次将其设置为冻结集合:
FrozenSet<Product> frozenProducts = ProductList.Products.ToFrozenSet();
frozenProducts.Add(new Product()
{
Id = 101,
Available = true,
Status = Status.Delivered,
Stock = 100,
Title = "Testing"
});
frozenProducts.Single(x => x.Title == "Meatballs").Stock = 23;
foreach (Product product in products.Where(x => x.Stock > 10))
Console.WriteLine($"{product.Title} ({product.Stock})");
frozenProducts 上的 Add() 方法出现错误:
但它真的更快吗?我做了一个小的基准测试:
public class Benchmarks
{
[Benchmark]
public void NormalList()
{
List<Product> products = ProductList.Products;
foreach (Product product in products)
{
Console.WriteLine(product.Title);
}
}
[Benchmark]
public void ForzenList()
{
FrozenSet<Product> frozenProducts = ProductList.Products.ToFrozenSet();
foreach (Product product in frozenProducts)
{
Console.WriteLine(product.Title);
}
}
}
两个测试都在做完全相同的事情,但一个是普通列表,另一个使用冻结的集合。这就是结果“:
结果是最小的,但测试也很小。
3:块
(.NET 6)
块是一种将集合拆分为较小组或特定大小的 “块” 的方法。这样,您可以将一长串项目分成更小的组,使其更易于使用。
我们的产品列表有 12 项。我们可以将其分成 3 个项目的组,并在 foreach 中处理每个块:
IEnumerable<Product[]> chunks = ProductList.Products.Chunk(3);
foreach (Product[] chunk in chunks)
{
foreach (Product product in chunk)
{
Console.WriteLine(product.Title);
}
}
块的好处:
处理可管理的零件比处理一大堆清单更容易。
它可以更有效地管理内存。
每个块都可以并行处理,这是一个很大的改进。
它改进了错误处理。某个 chunk 中的错误不会影响其他 chunk。您可以处理一个 chunk 中的错误,而不是整个列表中的错误。
由于您测试了数据集的特定部分,因此测试效率更高。
还有更多好处,但这些是最重要的......我认为。您知道在哪里可以找到有关此主题的更多信息。
4:专用锁类型
(.NET 9)
对于不熟悉 lock 机制的人:我们使用 lock 来控制对数据或对象的访问。当您使用缓存机制时,这非常方便,因为通常会忽略一件事。当有人进入应用程序并需要创建新的缓存项时,其他人应该等待,而不是在第一个人仍在创建所述项时尝试创建相同的缓存项。我们可以通过 lock 让其他人 'wait'。
如果触发了两个相同的请求,则可能会创建该项目两次,从而导致请求在毫秒后出现大异常。为避免这种情况,我们可以使用 lock 机制。它看起来像这样:
private readonly object _cacheLock = new();
public IEnumerable<Product> GetAll()
{
string key = "allmovies";
if (!memoryCache.TryGetValue(key, out List<Product>? products))
{
lock (_cacheLock)
{
products = ProductList.Products.ToList();
memoryCache.Set(key, products);
}
}
return products ?? [];
}
简而言之:对数据(products 和 memoryCache)的访问被锁定,直到完成。这样可以防止重复创建同一密钥的可能性。
但是用对象创建锁感觉有点......奇怪。C# 花了很长时间才解决这个问题。但现在,在 .NET 9 中,我们终于获得了专用锁!🥳
为什么这这么重要?嗯,专用锁类型会改进代码,使其更灵活,使代码更简洁,还可以提高性能。
变化很大吗?不!如果我将上面的代码更改为使用专用锁,我所要做的就是将 object 更改为 Lock:
// Change
private readonly object _cacheLock = new();
// To
private readonly Lock _cacheLock = new();
机会很小,但影响很大。
但是,既然他们这样做了,为什么不实现一个 awaitable lock 呢?即使使用专用锁,你也不能(仍然)使用 await。也许在 .NET 10 中?
5:需要 C# 11
(C# 11)
我们都知道 Required 属性。它确保类的某些属性是必需的......呃。但还有一个关键字 Required!当您尝试使用 Required 属性初始化类或对象,并且在初始化时未设置该属性时,这将给出编译错误。
Product 类的 Title 是关键字所必需的。如果我尝试创建带有标题的新产品,没什么特别的。当我尝试创建没有标题的 this 时... :
但是,当我删除关键字 Required 并添加属性 Required 时,创建没有 Title 的新产品不会出错。
如果你喜欢我的文章,请给我一个赞!谢谢