在阅读了一些关于产量、foreach、linq延迟执行和迭代器如何在C#中工作的内容之后。我决定尝试在一个小项目中优化一个基于属性的验证机制。结果:
private IEnumerable<string> GetPropertyErrors(PropertyInfo property)
{
// where Entity is the current object instance
string propertyValue = property.GetValue(Entity)?.ToString();
foreach (var attribute in property.GetCustomAttributes().OfType<ValidationAttribute>())
{
if (!attribute.IsValid(propertyValue))
{
yield return $"Error: {property.Name} {attribute.ErrorMessage}";
}
}
}
// inside another method
foreach(string error in GetPropertyErrors(property))
{
// Some display/insert log operation
}我发现这很慢,但这也可能是由于反射或大量的属性需要处理。
所以我的问题是..。是最优的还是很好地利用了懒惰的加载机制?或者我遗漏了一些东西,只是浪费了大量的资源。
注意:代码意图本身并不重要,我关心的是在其中使用延迟加载。
发布于 2016-02-02 13:01:29
懒惰加载不是特定于C#或实体框架的东西。这是一种常见的模式,它允许推迟一些数据加载。延期是指不立即装货。当你需要的时候,一些样本:
延迟加载并不总是意味着推迟加载,直到您真正需要数据。在真正需要这些数据之前,可能会在后台线程中进行加载。例如,你可能永远不会滚动到网页底部来查看页脚图像。延迟加载只意味着推迟。C#枚举器可以帮助您做到这一点。考虑获取目录中的文件列表:
string[] files = Directory.GetFiles("D:");
IEnumerable<string> filesEnumerator = Directory.EnumerateFiles("D:");第一种方法返回文件数组。这意味着目录应该获取其所有文件,并将它们的名称保存到数组中,然后才能获得第一个文件名。这就像在您看到文档之前加载所有图像一样。
第二种方法使用枚举器--当您请求下一个文件名时,它将一个一个地返回文件。这意味着枚举数将立即返回,而不需要获取所有文件并将其保存到某个集合中。当你需要的时候你可以一个一个地处理文件。在这里,获取文件列表被推迟。
但你应该小心点。如果未推迟基础操作,则返回枚举数不会给您带来任何好处。例如。
public IEnumerable<string> EnumerateFiles(string path)
{
foreach(string file in Directory.GetFiles(path))
yield return file;
}这里使用GetFiles方法,它在返回文件名之前填充文件名数组。因此,一个接一个地生成文件不会给您速度带来任何好处。
顺便说一句,在您的情况下,您有完全相同的问题-- GetCustomAttributes扩展内部使用Attribute.GetCustomAttributes方法,它返回属性数组。因此,你不会减少获得第一结果的时间。
发布于 2016-02-02 12:38:01
这并不是“延迟加载”这个术语在.NET中的普遍用法。“延迟加载”最常用于以下几个方面:
public SomeType SomeValue
{
get
{
if (_backingField == null)
_backingField = RelativelyLengthyCalculationOrRetrieval();
return _backingField;
}
}而不是仅在构造实例时设置_backingField。它的优点是,在SomeValue从未被访问的情况下,它不需要花费任何费用,而当它被访问时,代价会稍微大一点。因此,当SomeValue未被调用的几率相对较高时,这是有利的,并且通常是不利的,除非有一些例外(当我们可能关心在实例创建和第一次调用SomeValue之间完成事情的速度时)。
在这里,我们推迟执行死刑。这是相似的,但不完全一样。当您调用GetPropertyErrors(property)而不是接收所有错误的集合时,您会收到一个对象,该对象可以在被请求时找到这些错误。
它将始终节省获得第一个这样的项目所需的时间,因为它允许您立即对其采取行动,而不是等到它完成处理。
它总是会减少内存的使用,因为它不会将内存花在集合上。
它还将总共节省时间,因为创建集合不会花费时间。
但是,如果您需要访问它不止一次,那么当一个集合仍然具有相同的结果时,它将不得不再次计算它们(不像延迟加载,加载其结果并存储它们以供随后的重用)。
如果你很少想要达到相同的结果,这通常是一个胜利。
如果你总是想要达到相同的结果,这通常是一种损失。
但是,如果有时要访问相同的结果集,则可以将是否缓存的决定传递给调用方,只需使用调用GetPropertyErrors()并直接对结果执行操作,而是重复使用调用ToList(),然后对该列表重复操作。
因此,不发送列表的方法更灵活,允许调用代码决定哪种方法对它的特定使用更为有效。
您还可以将其与延迟加载结合起来:
private IEnumerable<string> LazyLoadedEnumerator()
{
if (_store == null)
return StoringCalculatingEnumerator();
return _store;
}
private IEnumerable<string> StoringCalculatingEnumerator()
{
List<string> store = new List<string>();
foreach(string str in SomethingThatCalculatesTheseStrings())
{
yield return str;
store.Add(str);
}
_store = store;
}然而,这种组合在实践中很少有用。
通常,从延迟评估开始作为正常的方法,并在调用链上进一步决定是否存储结果。但是,一个例外情况是,在开始之前,您可以知道结果的大小(因为在检查属性之前,您不知道是否会添加元素)。在这种情况下,在创建列表的方式上有可能提高性能,因为您可以提前设置它的容量。不过,这是一个微观优化,只有当你也知道你也总是想要做一个列表,并且不会在大的事情计划中节省那么多的时候才适用。
https://stackoverflow.com/questions/35153049
复制相似问题