首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >理解C#中的延迟加载优化

理解C#中的延迟加载优化
EN

Stack Overflow用户
提问于 2016-02-02 12:05:27
回答 2查看 1.4K关注 0票数 2

在阅读了一些关于产量、foreach、linq延迟执行和迭代器如何在C#中工作的内容之后。我决定尝试在一个小项目中优化一个基于属性的验证机制。结果:

代码语言:javascript
复制
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
}

我发现这很慢,但这也可能是由于反射或大量的属性需要处理。

所以我的问题是..。是最优的还是很好地利用了懒惰的加载机制?或者我遗漏了一些东西,只是浪费了大量的资源。

注意:代码意图本身并不重要,我关心的是在其中使用延迟加载。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-02-02 13:01:29

懒惰加载不是特定于C#或实体框架的东西。这是一种常见的模式,它允许推迟一些数据加载。延期是指不立即装货。当你需要的时候,一些样本:

  • 在(Word)文档中加载图像。文档可能很大,可以包含数千张图像。如果您要在打开文档时加载所有这些文件,则可能需要大量时间。没有人希望在加载文件时坐着观看30秒。在web浏览器中也使用同样的方法--资源不随页面正文一起发送。浏览器延迟加载资源。
  • 加载对象的图表。它可能是来自数据库、文件系统对象等的对象。加载完整图可能等于将所有数据库内容加载到内存中。需要多长时间?有效率吗?不是的。如果您正在构建某个文件系统资源管理器,您会在开始使用之前加载系统中每个文件的信息吗?如果只加载有关当前目录的信息(可能是直接子目录),则速度要快得多。

延迟加载并不总是意味着推迟加载,直到您真正需要数据。在真正需要这些数据之前,可能会在后台线程中进行加载。例如,你可能永远不会滚动到网页底部来查看页脚图像。延迟加载只意味着推迟。C#枚举器可以帮助您做到这一点。考虑获取目录中的文件列表:

代码语言:javascript
复制
string[] files = Directory.GetFiles("D:");
IEnumerable<string> filesEnumerator = Directory.EnumerateFiles("D:");

第一种方法返回文件数组。这意味着目录应该获取其所有文件,并将它们的名称保存到数组中,然后才能获得第一个文件名。这就像在您看到文档之前加载所有图像一样。

第二种方法使用枚举器--当您请求下一个文件名时,它将一个一个地返回文件。这意味着枚举数将立即返回,而不需要获取所有文件并将其保存到某个集合中。当你需要的时候你可以一个一个地处理文件。在这里,获取文件列表被推迟。

但你应该小心点。如果未推迟基础操作,则返回枚举数不会给您带来任何好处。例如。

代码语言:javascript
复制
public IEnumerable<string> EnumerateFiles(string path)
{
    foreach(string file in Directory.GetFiles(path))
        yield return file;
}

这里使用GetFiles方法,它在返回文件名之前填充文件名数组。因此,一个接一个地生成文件不会给您速度带来任何好处。

顺便说一句,在您的情况下,您有完全相同的问题-- GetCustomAttributes扩展内部使用Attribute.GetCustomAttributes方法,它返回属性数组。因此,你不会减少获得第一结果的时间。

票数 5
EN

Stack Overflow用户

发布于 2016-02-02 12:38:01

这并不是“延迟加载”这个术语在.NET中的普遍用法。“延迟加载”最常用于以下几个方面:

代码语言:javascript
复制
public SomeType SomeValue
{
  get
  {
    if (_backingField == null)
      _backingField = RelativelyLengthyCalculationOrRetrieval();
    return _backingField;
  }
}

而不是仅在构造实例时设置_backingField。它的优点是,在SomeValue从未被访问的情况下,它不需要花费任何费用,而当它被访问时,代价会稍微大一点。因此,当SomeValue未被调用的几率相对较高时,这是有利的,并且通常是不利的,除非有一些例外(当我们可能关心在实例创建和第一次调用SomeValue之间完成事情的速度时)。

在这里,我们推迟执行死刑。这是相似的,但不完全一样。当您调用GetPropertyErrors(property)而不是接收所有错误的集合时,您会收到一个对象,该对象可以在被请求时找到这些错误。

它将始终节省获得第一个这样的项目所需的时间,因为它允许您立即对其采取行动,而不是等到它完成处理。

它总是会减少内存的使用,因为它不会将内存花在集合上。

它还将总共节省时间,因为创建集合不会花费时间。

但是,如果您需要访问它不止一次,那么当一个集合仍然具有相同的结果时,它将不得不再次计算它们(不像延迟加载,加载其结果并存储它们以供随后的重用)。

如果你很少想要达到相同的结果,这通常是一个胜利。

如果你总是想要达到相同的结果,这通常是一种损失。

但是,如果有时要访问相同的结果集,则可以将是否缓存的决定传递给调用方,只需使用调用GetPropertyErrors()并直接对结果执行操作,而是重复使用调用ToList(),然后对该列表重复操作。

因此,不发送列表的方法更灵活,允许调用代码决定哪种方法对它的特定使用更为有效。

您还可以将其与延迟加载结合起来:

代码语言:javascript
复制
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;
}

然而,这种组合在实践中很少有用。

通常,从延迟评估开始作为正常的方法,并在调用链上进一步决定是否存储结果。但是,一个例外情况是,在开始之前,您可以知道结果的大小(因为在检查属性之前,您不知道是否会添加元素)。在这种情况下,在创建列表的方式上有可能提高性能,因为您可以提前设置它的容量。不过,这是一个微观优化,只有当你也知道你也总是想要做一个列表,并且不会在大的事情计划中节省那么多的时候才适用。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/35153049

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档