我正在编写一个适用于文件系统的应用程序。当应用程序首次启动时,它会运行一个快速例程,将请求的文件和文件夹加载到内存中,以便以后(时间密集)处理。 (见下面的代码)。此时它会计算出要处理的文件数量,这对于显示进度条非常重要。
一旦我有了计数和文件数据,我需要存储数据以供以后处理(例如作为全局变量或属性或类)。问题在于它由于使用LINQ而被存储为“var”。当我破坏并检查变量时,它被存储为SelectQueryOperator和AnonymousType的相当复杂的混合。
我的第一个想法是继续循环数据并将其转换为我可以存储为List<>的简单数据(例如存储文件名和路径),但这样做需要几分钟 - 最多10分钟或更多 - 处理。我将不得不在以后循环遍历所有数据以进行处理,并且我的用户无法坐下来等待首先构建列表。
如何存储这些数据,以便我可以在以后访问它而无需先将其转换为其他内容?
var fileNames =
from dir in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
select dir;
var fileContents = from file in fileNames.AsParallel()
// Use AsOrdered to preserve source ordering
let extension = Path.GetExtension(file)
let Text = File.ReadAllText(file)
select new { Text, FileName = file };
答案 0 :(得分:2)
问题在于它因为使用LINQ而被存储为“var”。
不,LINQ没有任何关于要求使用var
的内容。基本上var
允许您在方法中以强类型方式使用匿名类型。
您需要做的就是将您的匿名类型转换为命名类型,并且您将拥有与var
完全相同的性能。您使用ToList
时所看到的差异只是在您评估查询之前,它实际上不会做任何事情 - 我怀疑您根本没有访问文件系统。 (目前尚不清楚为什么首先有Directory.EnumerateFiles
的查询表达式。)
要么你需要提前加载数据要么你没有 - 你的问题并不是很清楚,但var
部分完全正交。
另外,在文件系统上使用并行处理可能会阻碍而不是帮助。
答案 1 :(得分:1)
您不能将var
用于任何非本地变量。 (This is why.)如果你真的真的讨厌需要维护代码的人,只需将其存储为object
或dynamic
并使用其中一种可能的黑客来获取来自匿名类型的信息已存储为object
,但这可能不是一个好主意。
真的,最好的办法是创建一个具有Text
和FileName
属性的新类型,并使用它而不是匿名类型。对于未来的开发者来说,这是最简单和最不重要的选择。
答案 2 :(得分:1)
让我们稍微简化一下,并在我们可以的地方明确var
..
var fileNames =
from dir in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
select dir;
这与:
完全相同var fileNames = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories);
与...完全相同:
IEnumerable<string> fileNames = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
现在:
var fileContents = from file in fileNames.AsParallel()
// Use AsOrdered to preserve source ordering
let extension = Path.GetExtension(file)
let Text = File.ReadAllText(file)
select new { Text, FileName = file };
寻找单行奇迹通常不会有助于提高可读性,但为了讨论起见,它有助于将我们的对象创建在一个地方:
var fileContents = from file in fileNames.AsParallel()
select new { Text = File.ReadAllText(file), FileName = Path.GetExtension(file) };
对于匿名ParallelQuery<T>
,这是T
。为了实现我们可以存储的东西,我们需要停止使用匿名类:
private class NameAndContents
{
public string Text{get;set;}
public string FileName{get;set;}
}
ParallelQuery<NameAndContents> fileContents = from file in fileNames.AsParallel()
select new NameAndContents{ Text = File.ReadAllText(file), FileName = Path.GetExtension(file) };
现在没有什么能阻止你将其存储在ParallelQuery<NameAndContents>
类型的字段中。
您可能希望通过两种方式检查逻辑:
Directory.EnumerateFiles
的工作方式是,它需要知道给定迭代的值才能计算下一个迭代。 (它基于FindNextFile
Windows API函数)。这使得并行化很差。 ReadAllText
平衡所涉及的固有等待多少是很难预测的。我不仅要针对非并行版本进行测试,而且在进行任何更改之后我会重新测试,因为任何更改都会以新的方式消除这种平衡。
这里最大的打击是ReadAllText
。如果完全可以用更加按需的方式替换文本,那么这可能是一个巨大的胜利。
答案 3 :(得分:0)
预先加载所有数据并保留它以供以后处理几乎总是错误的想法。你应该做的是逐个加载文件并随时处理它们,在这种情况下你不需要存储任何东西。
要解决问题的字母:您只需将操作结果投影到匿名类型以外的任何其他内容。例如,您可以创建一个类:
class FileData
{
string FileName { get; set; }
string Contents { get; set; }
}
var fileContents = from file in fileNames
select new FileData
{
FileName = Path.GetExtension(file),
Contents = File.ReadAllText(file)
};
只要您不在此变量上调用.ToList()
或类似内容,您就可以动态枚举文件及其内容。
旁注:我删除了.AsParallel()
调用,因为此操作的瓶颈是文件系统,而不是CPU。
答案 4 :(得分:0)
有什么问题
List<string> files = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories).ToList();
你真的需要真正阅读每个文件吗?
顺便说一句,var不是动态类型,var只是“编译器的编译器简写,在这里为我写下右侧的类型所以我可以避免像
这样的代码List<type> a = new List<type>()
每次看到“var”时,都可以用实际类型替换它。
我也没有看到“AsParallel”在这里应该如何帮助。