Практическое руководство. Группировка файлов по расширению (LINQ)
Обновлен: Ноябрь 2007
В этом примере показано использование LINQ для выполнения расширенной группировки и сортировки списков файлов или папок. В нем также показано, как разбивать на страницы выходные данные в окне консоли с помощью методов Skip<TSource> и Take<TSource>.
Пример
В следующем запросе показана группировка содержимого в указанном дереве каталогов по расширению имени файла.
Module GroupByExtension
Public Sub Main()
' Root folder to query, along with all subfolders.
Dim startFolder As String = "C:\program files\Microsoft Visual Studio 9.0\VB\"
' Used in WriteLine() to skip over startfolder in output lines.
Dim rootLength As Integer = startFolder.Length
' Take a snapshot of the file system.
Dim fileList As IEnumerable(Of System.IO.FileInfo) = GetFiles(startFolder)
' Create the query.
Dim queryGroupByExt = From file In fileList _
Group By file.Extension.ToLower() Into fileGroup = Group _
Order By ToLower _
Select fileGroup
' Execute the query. By storing the result we can
' page the display with good performance.
Dim groupByExtList = queryGroupByExt.ToList()
' Display one group at a time. If the number of
' entries is greater than the number of lines
' in the console window, then page the output.
Dim trimLength = startFolder.Length
PageOutput(groupByExtList, trimLength)
End Sub
' Function to retrieve a list of files. Note that this is a copy
' of the file information.
Function GetFiles(ByVal root As String) As IEnumerable(Of System.IO.FileInfo)
Return From file In My.Computer.FileSystem.GetFiles _
(root, FileIO.SearchOption.SearchAllSubDirectories, "*.*") _
Select New System.IO.FileInfo(file)
End Function
' Pages console diplay for large query results. No more than one group per page.
' This sub specifically works with group queries of FileInfo objects
' but can be modified for any type.
Sub PageOutput(ByVal groupQuery, ByVal charsToSkip)
' "3" = 1 line for extension key + 1 for "Press any key" + 1 for input cursor.
Dim numLines As Integer = Console.WindowHeight - 3
' Flag to indicate whether there are more results to diplay
Dim goAgain As Boolean = True
For Each fg As IEnumerable(Of System.IO.FileInfo) In groupQuery
' Start a new extension at the top of a page.
Dim currentLine As Integer = 0
Do While (currentLine < fg.Count())
Console.Clear()
Console.WriteLine(fg(0).Extension)
' Get the next page of results
' No more than one filename per page
Dim resultPage = From file In fg _
Skip currentLine Take numLines
' Execute the query. Trim the display output.
For Each line In resultPage
Console.WriteLine(vbTab & line.FullName.Substring(charsToSkip))
Next
' Advance the current position
currentLine = numLines + currentLine
' Give the user a chance to break out of the loop
Console.WriteLine("Press any key for next page or the 'End' key to exit.")
Dim key As ConsoleKey = Console.ReadKey().Key
If key = ConsoleKey.End Then
goAgain = False
Exit For
End If
Loop
Next
End Sub
End Module
class GroupByExtension
{
// This query will sort all the files under the specified folder
// and subfolder into groups keyed by the file extension.
private static void Main()
{
// Take a snapshot of the file system.
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\Common7";
// Used in WriteLine to trim output lines.
int trimLength = startFolder.Length;
// Take a snapshot of the file system.
IEnumerable<System.IO.FileInfo> fileList = GetFiles(startFolder);
// Create the query.
var queryGroupByExt =
from file in fileList
group file by file.Extension.ToLower() into fileGroup
orderby fileGroup.Key
select fileGroup;
// Display one group at a time. If the number of
// entries is greater than the number of lines
// in the console window, then page the output.
PageOutput(trimLength, queryGroupByExt);
}
// This method specifically handles group queries of FileInfo objects with string keys.
// It can be modified to work for any long listings of data. Note that explicit typing
// must be used in method signatures. The groupbyExtList parameter is a query that produces
// groups of FileInfo objects with string keys.
private static void PageOutput( int rootLength,
IEnumerable<System.Linq.IGrouping<string, System.IO.FileInfo>> groupByExtList)
{
// Flag to break out of paging loop.
bool goAgain = true;
// "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
int numLines = Console.WindowHeight - 3;
// Iterate through the outer collection of groups.
foreach (var filegroup in groupByExtList)
{
// Start a new extension at the top of a page.
int currentLine = 0;
// Output only as many lines of the current group as will fit in the window.
do
{
Console.Clear();
Console.WriteLine(filegroup.Key == String.Empty ? "[none]" : filegroup.Key);
// Get 'numLines' number of items starting at number 'currentLine'.
var resultPage = filegroup.Skip(currentLine).Take(numLines);
//Execute the resultPage query
foreach (var f in resultPage)
{
Console.WriteLine("\t{0}", f.FullName.Substring(rootLength));
}
// Increment the line counter.
currentLine += numLines;
// Give the user a chance to escape.
Console.WriteLine("Press any key to continue or the 'End' key to break...");
ConsoleKey key = Console.ReadKey().Key;
if (key == ConsoleKey.End)
{
goAgain = false;
break;
}
} while (currentLine < filegroup.Count());
if (goAgain == false)
break;
}
}
// This method assumes that the application has discovery
// permissions for all folders under the specified path.
static IEnumerable<System.IO.FileInfo> GetFiles(string path)
{
if (!System.IO.Directory.Exists(path))
throw new System.IO.DirectoryNotFoundException();
string[] fileNames = null;
List<System.IO.FileInfo> files = new List<System.IO.FileInfo>();
fileNames = System.IO.Directory.GetFiles(path, "*.*", System.IO.SearchOption.AllDirectories);
foreach (string name in fileNames)
{
files.Add(new System.IO.FileInfo(name));
}
return files;
}
}
Выходные данные этой программы могут быть длинными в зависимости от структуры локальной файловой системы и указанного значения startFolder. Для просмотра всех результаты в этом примере демонстрируется способ их пролистывания. Те же методы могут применяться к приложениям Windows и веб-приложениям. Обратите внимание, что поскольку код пролистывает элементы в группе, требуется вложенный цикл foreach. Также существует некоторая дополнительная логика для вычисления текущей позиции в списке и предоставления пользователю возможности остановить разбиение по страницам и выйти из программы. В данном конкретном случае запрос, разбивающий по страницам, выполняется к кэшированным результатам из исходного запроса. В другом контексте, таком как LINQ to SQL, подобное кэширование не требуется.
Компиляция кода
Создайте проект Visual Studio, предназначенный для .NET Framework версии 3.5. По умолчанию в этом проекте имеется ссылка на файл System.Core.dll и директива using (C#) или оператор Imports (Visual Basic) для пространства имен System.Linq. При работе с проектами C# добавьте директиву using для пространства имен System.IO.
Скопируйте этот код в проект.
Нажмите клавишу F5, чтобы скомпилировать и выполнить программу.
Нажмите любую клавишу для выхода из окна консоли.
Отказоустойчивость
Для интенсивного использования операций запроса к содержимому нескольких типов документов и файлов, рассмотрите возможность использования средства поиска Windows Desktop Search.