Freigeben über


Vorgehensweise: Verwenden von LINQ zum Abfragen von Dateien und Verzeichnissen

Viele Dateisystemvorgänge sind im Wesentlichen Abfragen und eignen sich daher gut für den LINQ-Ansatz. Diese Abfragen sind nicht destruktiv. Sie ändern nicht den Inhalt der ursprünglichen Dateien oder Ordner. Abfragen sollten keine Nebenwirkungen verursachen. Im Allgemeinen sollten alle Code (einschließlich Abfragen, die Erstellungs-/Aktualisierungs-/Löschvorgänge ausführen), die Quelldaten ändert, vom Code getrennt bleiben, der nur die Daten abfragt.

Es gibt einige Komplexität beim Erstellen einer Datenquelle, die den Inhalt des Dateisystems genau darstellt und Ausnahmen ordnungsgemäß behandelt. In den Beispielen in diesem Abschnitt wird eine Momentaufnahmeauflistung von FileInfo Objekten erstellt, die alle Dateien unter einem angegebenen Stammordner und allen Unterordnern darstellen. Der tatsächliche Status jeder FileInfo kann sich in der Zeit zwischen dem Starten und Beenden der Ausführung einer Abfrage ändern. Sie können beispielsweise eine Liste von FileInfo Objekten erstellen, die als Datenquelle verwendet werden sollen. Wenn Sie versuchen, auf die Eigenschaft Length in einer Abfrage zuzugreifen, versucht das Objekt FileInfo, auf das Dateisystem zuzugreifen, um den Wert von Length zu aktualisieren. Wenn die Datei nicht mehr existiert, erhalten Sie ein FileNotFoundException in Ihrer Abfrage, auch wenn Sie nicht direkt das Dateisystem abfragen.

So wird's ausgeführt: Abfragen nach Dateien mit einem angegebenen Attribut oder Namen

In diesem Beispiel wird gezeigt, wie Alle Dateien mit einer angegebenen Dateinamenerweiterung (z. B. ".txt") in einer angegebenen Verzeichnisstruktur gefunden werden. Außerdem wird gezeigt, wie die neueste oder älteste Datei im Dateibaum abhängig von der Erstellungszeit zurückgegeben wird. Möglicherweise müssen Sie die erste Zeile vieler Beispiele ändern, unabhängig davon, ob Sie diesen Code entweder unter Windows, Mac oder einem Linux-System ausführen.

string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

DirectoryInfo dir = new DirectoryInfo(startFolder);
var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);

var fileQuery = from file in fileList
                where file.Extension == ".txt"
                orderby file.Name
                select file;

// Uncomment this block to see the full query
// foreach (FileInfo fi in fileQuery)
// {
//    Console.WriteLine(fi.FullName);
// }

var newestFile = (from file in fileQuery
                  orderby file.CreationTime
                  select new { file.FullName, file.CreationTime })
                  .Last();

Console.WriteLine($"\r\nThe newest .txt file is {newestFile.FullName}. Creation time: {newestFile.CreationTime}");

So gruppieren Sie Dateien nach Erweiterung

In diesem Beispiel wird gezeigt, wie LINQ verwendet werden kann, um erweiterte Gruppierungs- und Sortiervorgänge in Listen von Dateien oder Ordnern auszuführen. Außerdem wird gezeigt, wie die Seitenausgabe im Konsolenfenster mithilfe der Skip und Take Methoden erfolgt.

Die folgende Abfrage zeigt, wie der Inhalt einer angegebenen Verzeichnisstruktur anhand der Dateinamenerweiterung gruppiert wird.

string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

int trimLength = startFolder.Length;

DirectoryInfo dir = new DirectoryInfo(startFolder);

var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);

var queryGroupByExt = from file in fileList
                      group file by file.Extension.ToLower() into fileGroup
                      orderby fileGroup.Count(), fileGroup.Key
                      select fileGroup;

// Iterate through the outer collection of groups.
foreach (var filegroup in queryGroupByExt.Take(5))
{
    Console.WriteLine($"Extension: {filegroup.Key}");
    var resultPage = filegroup.Take(20);

    //Execute the resultPage query
    foreach (var f in resultPage)
    {
        Console.WriteLine($"\t{f.FullName.Substring(trimLength)}");
    }
    Console.WriteLine();
}

Die Ausgabe dieses Programms kann je nach den Details des lokalen Dateisystems und der Einstellung für startFolder lang sein. Um die Anzeige aller Ergebnisse zu aktivieren, zeigt dieses Beispiel, wie Sie ergebnisse durchblättern. Eine geschachtelte foreach Schleife ist erforderlich, da jede Gruppe separat aufgezählt wird.

So fragen Sie nach der Gesamtanzahl von Bytes in einem Satz von Ordnern

In diesem Beispiel wird gezeigt, wie sie die Gesamtanzahl der Bytes abrufen, die von allen Dateien in einem angegebenen Ordner und allen unterordnern verwendet werden. Die Sum Methode fügt die Werte aller elemente hinzu, die in der select Klausel ausgewählt sind. Sie können diese Abfrage ändern, um die größte oder kleinste Datei in der angegebenen Verzeichnisstruktur abzurufen, indem Sie die Min- oder Max-Methode anstelle von Sum aufrufen.

string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

var fileList = Directory.GetFiles(startFolder, "*.*", SearchOption.AllDirectories);

var fileQuery = from file in fileList
                let fileLen = new FileInfo(file).Length
                where fileLen > 0
                select fileLen;

// Cache the results to avoid multiple trips to the file system.
long[] fileLengths = fileQuery.ToArray();

// Return the size of the largest file
long largestFile = fileLengths.Max();

// Return the total number of bytes in all the files under the specified folder.
long totalBytes = fileLengths.Sum();

Console.WriteLine($"There are {totalBytes} bytes in {fileList.Count()} files under {startFolder}");
Console.WriteLine($"The largest file is {largestFile} bytes.");

In diesem Beispiel wird das vorherige Beispiel so erweitert, dass folgendes ausgeführt wird:

  • So rufen Sie die Größe in Byte der größten Datei ab.
  • So rufen Sie die Größe in Byte der kleinsten Datei ab.
  • So rufen Sie die FileInfo größte oder kleinste Objektdatei aus einem oder mehreren Ordnern unter einem angegebenen Stammordner ab.
  • So rufen Sie eine Sequenz wie die 10 größten Dateien ab.
  • So ordnen Sie Dateien basierend auf ihrer Dateigröße in Byte in Gruppen an, wobei Dateien ignoriert werden, die kleiner als eine angegebene Größe sind.

Das folgende Beispiel enthält fünf separate Abfragen, die zeigen, wie Dateien je nach ihrer Dateigröße in Byte abgefragt und gruppiert werden. Sie können diese Beispiele ändern, um die Abfrage auf eine andere Eigenschaft des FileInfo Objekts zu basieren.

// Return the FileInfo object for the largest file
// by sorting and selecting from beginning of list
FileInfo longestFile = (from file in fileList
                        let fileInfo = new FileInfo(file)
                        where fileInfo.Length > 0
                        orderby fileInfo.Length descending
                        select fileInfo
                        ).First();

Console.WriteLine($"The largest file under {startFolder} is {longestFile.FullName} with a length of {longestFile.Length} bytes");

//Return the FileInfo of the smallest file
FileInfo smallestFile = (from file in fileList
                         let fileInfo = new FileInfo(file)
                         where fileInfo.Length > 0
                         orderby fileInfo.Length ascending
                         select fileInfo
                        ).First();

Console.WriteLine($"The smallest file under {startFolder} is {smallestFile.FullName} with a length of {smallestFile.Length} bytes");

//Return the FileInfos for the 10 largest files
var queryTenLargest = (from file in fileList
                       let fileInfo = new FileInfo(file)
                       let len = fileInfo.Length
                       orderby len descending
                       select fileInfo
                      ).Take(10);

Console.WriteLine($"The 10 largest files under {startFolder} are:");

foreach (var v in queryTenLargest)
{
    Console.WriteLine($"{v.FullName}: {v.Length} bytes");
}

// Group the files according to their size, leaving out
// files that are less than 200000 bytes.
var querySizeGroups = from file in fileList
                      let fileInfo = new FileInfo(file)
                      let len = fileInfo.Length
                      where len > 0
                      group fileInfo by (len / 100000) into fileGroup
                      where fileGroup.Key >= 2
                      orderby fileGroup.Key descending
                      select fileGroup;

foreach (var filegroup in querySizeGroups)
{
    Console.WriteLine($"{filegroup.Key}00000");
    foreach (var item in filegroup)
    {
        Console.WriteLine($"\t{item.Name}: {item.Length}");
    }
}

Um ein oder mehrere vollständige FileInfo Objekte zurückzugeben, muss die Abfrage zuerst die einzelnen Objekte in der Datenquelle untersuchen und dann nach dem Wert ihrer Length-Eigenschaft sortieren. Anschließend kann sie die einzelne oder die Sequenz mit den größten Längen zurückgeben. Verwenden Sie First, um das erste Element in einer Liste zurückzugeben. Verwenden Sie Take, um die ersten n Elemente zurückzugeben. Geben Sie eine absteigende Sortierreihenfolge an, um die kleinsten Elemente am Anfang der Liste zu platzieren.

So suchen Sie nach doppelten Dateien in einem Verzeichnisbaum

Manchmal können Dateien, die denselben Namen haben, in mehreren Ordnern gespeichert werden. In diesem Beispiel wird gezeigt, wie Sie solche doppelten Dateinamen unter einem angegebenen Stammordner abfragen. Das zweite Beispiel zeigt, wie Sie dateien abfragen, deren Größe und LastWrite-Uhrzeit ebenfalls übereinstimmen.

string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

DirectoryInfo dir = new DirectoryInfo(startFolder);

IEnumerable<FileInfo> fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);

// used in WriteLine to keep the lines shorter
int charsToSkip = startFolder.Length;

// var can be used for convenience with groups.
var queryDupNames = from file in fileList
                    group file.FullName.Substring(charsToSkip) by file.Name into fileGroup
                    where fileGroup.Count() > 1
                    select fileGroup;

foreach (var queryDup in queryDupNames.Take(20))
{
    Console.WriteLine($"Filename = {(queryDup.Key.ToString() == string.Empty ? "[none]" : queryDup.Key.ToString())}");

    foreach (var fileName in queryDup.Take(10))
    {
        Console.WriteLine($"\t{fileName}");
    }   
}

Die erste Abfrage verwendet einen Schlüssel, um eine Übereinstimmung zu bestimmen. Es findet Dateien, die denselben Namen haben, aber deren Inhalt möglicherweise anders ist. Die zweite Abfrage verwendet einen zusammengesetzten Schlüssel, um mit drei Eigenschaften des FileInfo Objekts abzugleichen. Diese Abfrage ist viel wahrscheinlicher, dass Dateien mit demselben Namen und ähnlichen oder identischen Inhalten gefunden werden.

    string startFolder = """C:\Program Files\dotnet\sdk""";
    // Or
    // string startFolder = "/usr/local/share/dotnet/sdk";

    // Make the lines shorter for the console display
    int charsToSkip = startFolder.Length;

    // Take a snapshot of the file system.
    DirectoryInfo dir = new DirectoryInfo(startFolder);
    IEnumerable<FileInfo> fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);

    // Note the use of a compound key. Files that match
    // all three properties belong to the same group.
    // A named type is used to enable the query to be
    // passed to another method. Anonymous types can also be used
    // for composite keys but cannot be passed across method boundaries
    //
    var queryDupFiles = from file in fileList
                        group file.FullName.Substring(charsToSkip) by
                        (Name: file.Name, LastWriteTime: file.LastWriteTime, Length: file.Length )
                        into fileGroup
                        where fileGroup.Count() > 1
                        select fileGroup;

    foreach (var queryDup in queryDupFiles.Take(20))
    {
        Console.WriteLine($"Filename = {(queryDup.Key.ToString() == string.Empty ? "[none]" : queryDup.Key.ToString())}");

        foreach (var fileName in queryDup)
        {
            Console.WriteLine($"\t{fileName}");
        }
    }
}

Abfragen des Inhalts von Textdateien in einem Ordner

In diesem Beispiel wird gezeigt, wie Sie alle Dateien in einer angegebenen Verzeichnisstruktur abfragen, jede Datei öffnen und deren Inhalt überprüfen. Diese Technik kann zum Erstellen von Indizes oder umgekehrten Indizes des Inhalts einer Verzeichnisstruktur verwendet werden. In diesem Beispiel wird eine einfache Zeichenfolgensuche ausgeführt. Komplexere Musterabgleiche können jedoch mit einem regulären Ausdruck durchgeführt werden.

string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";

DirectoryInfo dir = new DirectoryInfo(startFolder);

var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);

string searchTerm = "change";

var queryMatchingFiles = from file in fileList
                         where file.Extension == ".txt"
                         let fileText = File.ReadAllText(file.FullName)
                         where fileText.Contains(searchTerm)
                         select file.FullName;

// Execute the query.
Console.WriteLine($"""The term "{searchTerm}" was found in:""");
foreach (string filename in queryMatchingFiles)
{
    Console.WriteLine(filename);
}

So vergleichen Sie den Inhalt von zwei Ordnern

In diesem Beispiel werden drei Möglichkeiten zum Vergleichen von zwei Dateiauflistungen veranschaulicht:

  • Durch Abfragen nach einem booleschen Wert, der angibt, ob die beiden Dateilisten identisch sind.
  • Durch das Abfragen des Schnittpunkts werden die Dateien abgerufen, die sich in beiden Ordnern befinden.
  • Durch Abfragen der Unterschiedsmenge, um die Dateien abzurufen, die sich in einem Ordner befinden, aber nicht im anderen.

Die hier gezeigten Techniken können angepasst werden, um Sequenzen von Objekten beliebiger Art zu vergleichen.

Die FileComparer hier gezeigte Klasse veranschaulicht die Verwendung einer benutzerdefinierten Vergleichsklasse zusammen mit den Standardabfrageoperatoren. Die Klasse ist nicht für die Verwendung in realen Szenarien vorgesehen. Er verwendet lediglich den Namen und die Länge in Byte jeder Datei, um zu bestimmen, ob der Inhalt jedes Ordners identisch ist oder nicht. In einem realen Szenario sollten Sie diesen Vergleich ändern, um eine strengere Gleichheitsprüfung durchzuführen.

// This implementation defines a very simple comparison
// between two FileInfo objects. It only compares the name
// of the files being compared and their length in bytes.
class FileCompare : IEqualityComparer<FileInfo>
{
    public bool Equals(FileInfo? f1, FileInfo? f2)
    {
        return (f1?.Name == f2?.Name &&
                f1?.Length == f2?.Length);
    }

    // Return a hash that reflects the comparison criteria. According to the
    // rules for IEqualityComparer<T>, if Equals is true, then the hash codes must
    // also be equal. Because equality as defined here is a simple value equality, not
    // reference identity, it is possible that two or more objects will produce the same
    // hash code.
    public int GetHashCode(FileInfo fi)
    {
        string s = $"{fi.Name}{fi.Length}";
        return s.GetHashCode();
    }
}

public static void CompareDirectories()
{
    string pathA = """C:\Program Files\dotnet\sdk\8.0.104""";
    string pathB = """C:\Program Files\dotnet\sdk\8.0.204""";

    DirectoryInfo dir1 = new DirectoryInfo(pathA);
    DirectoryInfo dir2 = new DirectoryInfo(pathB);

    IEnumerable<FileInfo> list1 = dir1.GetFiles("*.*", SearchOption.AllDirectories);
    IEnumerable<FileInfo> list2 = dir2.GetFiles("*.*", SearchOption.AllDirectories);

    //A custom file comparer defined below
    FileCompare myFileCompare = new FileCompare();

    // This query determines whether the two folders contain
    // identical file lists, based on the custom file comparer
    // that is defined in the FileCompare class.
    // The query executes immediately because it returns a bool.
    bool areIdentical = list1.SequenceEqual(list2, myFileCompare);

    if (areIdentical == true)
    {
        Console.WriteLine("the two folders are the same");
    }
    else
    {
        Console.WriteLine("The two folders are not the same");
    }

    // Find the common files. It produces a sequence and doesn't
    // execute until the foreach statement.
    var queryCommonFiles = list1.Intersect(list2, myFileCompare);

    if (queryCommonFiles.Any())
    {
        Console.WriteLine($"The following files are in both folders (total number = {queryCommonFiles.Count()}):");
        foreach (var v in queryCommonFiles.Take(10))
        {
            Console.WriteLine(v.Name); //shows which items end up in result list
        }
    }
    else
    {
        Console.WriteLine("There are no common files in the two folders.");
    }

    // Find the set difference between the two folders.
    var queryList1Only = (from file in list1
                          select file)
                          .Except(list2, myFileCompare);

    Console.WriteLine();
    Console.WriteLine($"The following files are in list1 but not list2 (total number = {queryList1Only.Count()}):");
    foreach (var v in queryList1Only.Take(10))
    {
        Console.WriteLine(v.FullName);
    }

    var queryList2Only = (from file in list2
                          select file)
                          .Except(list1, myFileCompare);

    Console.WriteLine();
    Console.WriteLine($"The following files are in list2 but not list1 (total number = {queryList2Only.Count()}:");
    foreach (var v in queryList2Only.Take(10))
    {
        Console.WriteLine(v.FullName);
    }
}

So ordnen Sie die Felder einer durch Trennzeichen getrennten Datei neu an

Eine CSV-Datei (Durch Trennzeichen getrennte Werte) ist eine Textdatei, die häufig zum Speichern von Tabellenkalkulationsdaten oder anderen tabellarischen Daten verwendet wird, die durch Zeilen und Spalten dargestellt werden. Durch die Verwendung der Methode zum Trennen der Split Felder ist es einfach, CSV-Dateien mithilfe von LINQ abzufragen und zu bearbeiten. Tatsächlich kann dieselbe Technik verwendet werden, um die Teile jeder strukturierten Textzeile neu anzuordnen; sie ist nicht auf CSV-Dateien beschränkt.

Gehen Sie im folgenden Beispiel davon aus, dass die drei Spalten den Namen "Familienname", "Vorname" und "ID" darstellen. Die Felder sind auf der Grundlage der Familiennamen der Schüler in alphabetischer Reihenfolge angeordnet. Die Abfrage erzeugt eine neue Sequenz, in der die ID-Spalte zuerst angezeigt wird, gefolgt von einer zweiten Spalte, die den Vornamen und den Familiennamen des Kursteilnehmers kombiniert. Die Zeilen werden entsprechend dem ID-Feld neu angeordnet. Die Ergebnisse werden in einer neuen Datei gespeichert, und die ursprünglichen Daten werden nicht geändert. Der folgende Text zeigt den Inhalt der im folgenden Beispiel verwendeten spreadsheet1.csv Datei:

Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121

Der folgende Code liest die Quelldatei und ordnet jede Spalte in der CSV-Datei neu an, um die Reihenfolge der Spalten neu anzuordnen:

string[] lines = File.ReadAllLines("spreadsheet1.csv");

// Create the query. Put field 2 first, then
// reverse and combine fields 0 and 1 from the old field
IEnumerable<string> query = from line in lines
                            let fields = line.Split(',')
                            orderby fields[2]
                            select $"{fields[2]}, {fields[1]} {fields[0]}";

File.WriteAllLines("spreadsheet2.csv", query.ToArray());

/* Output to spreadsheet2.csv:
111, Svetlana Omelchenko
112, Claire O'Donnell
113, Sven Mortensen
114, Cesar Garcia
115, Debra Garcia
116, Fadi Fakhouri
117, Hanying Feng
118, Hugo Garcia
119, Lance Tucker
120, Terry Adams
121, Eugene Zabokritski
122, Michael Tucker
*/

So teilen Sie eine Datei mithilfe von Gruppen in viele Dateien auf

In diesem Beispiel wird eine Möglichkeit zum Zusammenführen der Inhalte zweier Dateien gezeigt und dann eine Reihe neuer Dateien erstellt, die die Daten auf neue Weise organisieren. Die Abfrage verwendet den Inhalt von zwei Dateien. Der folgende Text zeigt den Inhalt der ersten Datei ,names1.txt:

Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra

Die zweite Datei, names2.txt, enthält einen anderen Satz von Namen, von denen einige mit dem ersten Satz gemeinsam sind:

Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi

Der folgende Code fragt beide Dateien ab, übernimmt die Vereinigung beider Dateien und schreibt dann eine neue Datei für jede Gruppe, die durch den ersten Buchstaben des Familiennamens definiert wird:

string[] fileA = File.ReadAllLines("names1.txt");
string[] fileB = File.ReadAllLines("names2.txt");

// Concatenate and remove duplicate names
var mergeQuery = fileA.Union(fileB);

// Group the names by the first letter in the last name.
var groupQuery = from name in mergeQuery
                 let n = name.Split(',')[0]
                 group name by n[0] into g
                 orderby g.Key
                 select g;

foreach (var g in groupQuery)
{
    string fileName = $"testFile_{g.Key}.txt";

    Console.WriteLine(g.Key);

    using StreamWriter sw = new StreamWriter(fileName);
    foreach (var item in g)
    {
        sw.WriteLine(item);
        // Output to console for example purposes.
        Console.WriteLine($"   {item}");
    }
}
/* Output:
    A
       Aw, Kam Foo
    B
       Bankov, Peter
       Beebe, Ann
    E
       El Yassir, Mehdi
    G
       Garcia, Hugo
       Guy, Wey Yuan
       Garcia, Debra
       Gilchrist, Beth
       Giakoumakis, Leo
    H
       Holm, Michael
    L
       Liu, Jinghao
    M
       Myrcha, Jacek
       McLin, Nkenge
    N
       Noriega, Fabricio
    P
       Potra, Cristina
    T
       Toyoshima, Tim
 */

So verbinden Sie Inhalte aus unterschiedlichen Dateien

In diesem Beispiel wird gezeigt, wie Daten aus zwei durch Trennzeichen getrennten Dateien verknüpft werden, die einen gemeinsamen Wert gemeinsam nutzen, der als übereinstimmenden Schlüssel verwendet wird. Diese Technik kann nützlich sein, wenn Sie Daten aus zwei Kalkulationstabellen oder aus einer Kalkulationstabelle und aus einer Datei mit einem anderen Format in einer neuen Datei kombinieren müssen. Sie können das Beispiel so ändern, dass es mit jeder Art von strukturiertem Text funktioniert.

Der folgende Text zeigt den Inhalt von scores.csv. Die Datei stellt Tabellenkalkulationsdaten dar. Spalte 1 ist die Id des Kursteilnehmers, und die Spalten 2 bis 5 sind Testergebnisse.

111, 97, 92, 81, 60
112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

Der folgende Text zeigt den Inhalt von names.csv. Die Datei stellt eine Kalkulationstabelle dar, die den Familiennamen, Vornamen und die Schüleridentifikation enthält.

Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122

Verbinden Sie Inhalte aus unterschiedlichen Dateien, die verwandte Informationen enthalten. Die Datei names.csv enthält den Namen des Kursteilnehmers sowie eine ID-Nummer. Die Datei-scores.csv enthält die ID und einen Satz von vier Testergebnissen. Die folgende Abfrage verknüpft die Bewertungen mit den Kursteilnehmernamen mithilfe der ID als übereinstimmenden Schlüssel. Der Code wird im folgenden Beispiel gezeigt:

string[] names = File.ReadAllLines(@"names.csv");
string[] scores = File.ReadAllLines(@"scores.csv");

var scoreQuery = from name in names
                  let nameFields = name.Split(',')
                  from id in scores
                  let scoreFields = id.Split(',')
                  where Convert.ToInt32(nameFields[2]) == Convert.ToInt32(scoreFields[0])
                  select $"{nameFields[0]},{scoreFields[1]},{scoreFields[2]},{scoreFields[3]},{scoreFields[4]}";

Console.WriteLine("\r\nMerge two spreadsheets:");
foreach (string item in scoreQuery)
{
    Console.WriteLine(item);
}
Console.WriteLine($"{scoreQuery.Count()} total names in list");
/* Output:
Merge two spreadsheets:
Omelchenko, 97, 92, 81, 60
O'Donnell, 75, 84, 91, 39
Mortensen, 88, 94, 65, 91
Garcia, 97, 89, 85, 82
Garcia, 35, 72, 91, 70
Fakhouri, 99, 86, 90, 94
Feng, 93, 92, 80, 87
Garcia, 92, 90, 83, 78
Tucker, 68, 79, 88, 92
Adams, 99, 82, 81, 79
Zabokritski, 96, 85, 91, 60
Tucker, 94, 92, 91, 91
12 total names in list
 */

Berechnen von Spaltenwerten in einer CSV-Textdatei

In diesem Beispiel wird gezeigt, wie Aggregierte Berechnungen wie Summe, Mittelwert, Min und Max in den Spalten einer .csv Datei ausgeführt werden. Die hier gezeigten Beispielprinzipien können auf andere Typen von strukturiertem Text angewendet werden.

Der folgende Text zeigt den Inhalt von scores.csv. Angenommen, die erste Spalte stellt eine Schüler-ID dar, und nachfolgende Spalten stellen Ergebnisse aus vier Prüfungen dar.

111, 97, 92, 81, 60
112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91

Der folgende Text zeigt, wie Sie die Split Methode verwenden, um jede Textzeile in ein Array zu konvertieren. Jedes Arrayelement stellt eine Spalte dar. Schließlich wird der Text in jeder Spalte in seine numerische Darstellung konvertiert.

public static class SumColumns
{
    public static void ProcessColumns(string filePath, string seperator)
    {
        // Divide each exam into a group
        var exams = from line in MatrixFrom(filePath, seperator)
                    from score in line

                    // Identify the column number
                    let colNumber = Array.FindIndex(line, t => ReferenceEquals(score, t))

                    // The first column is the student ID, not the exam score
                    // so it needs to be excluded
                    where colNumber > 0

                    // Convert the score from string to int
                    // Group by column number, i.e. one group per exam
                    group double.Parse(score) by colNumber into g
                    select new
                    {
                        Title = $"Exam#{g.Key}",
                        Min = g.Min(),
                        Max = g.Max(),
                        Avg = Math.Round(g.Average(), 2),
                        Total = g.Sum()
                    };

        foreach (var exam in exams)
        {
            Console.WriteLine($"{exam.Title}\t"
            + $"Average:{exam.Avg,6}\t"
            + $"High Score:{exam.Max,3}\t"
            + $"Low Score:{exam.Min,3}\t"
            + $"Total:{exam.Total,5}");
        }
    }

    // Transform the file content to an IEnumerable of string arrays
    // like a matrix
    private static IEnumerable<string[]> MatrixFrom(string filePath, string seperator)
    {
        using StreamReader reader = File.OpenText(filePath);

        for (string? line = reader.ReadLine(); line is not null; line = reader.ReadLine())
        {
            yield return line.Split(seperator, StringSplitOptions.TrimEntries);
        }
    }
}

// Output:
// Exam#1  Average: 86.08  High Score: 99  Low Score: 35   Total: 1033
// Exam#2  Average: 86.42  High Score: 94  Low Score: 72   Total: 1037
// Exam#3  Average: 84.75  High Score: 91  Low Score: 65   Total: 1017
// Exam#4  Average: 76.92  High Score: 94  Low Score: 39   Total:  923

Wenn es sich bei der Datei um eine tabstopptrennte Datei handelt, aktualisieren Sie einfach das Argument in der SumColumns.ProcessColumns Methode auf \t.