Procedure: LINQ gebruiken om query's uit te voeren op bestanden en mappen

Veel bestandssysteembewerkingen zijn in wezen query's en zijn daarom goed geschikt voor de LINQ-benadering. Deze query's zijn niet-destructief. Ze wijzigen de inhoud van de oorspronkelijke bestanden of mappen niet. Query's mogen geen bijwerkingen veroorzaken. Over het algemeen moeten alle code (inclusief query's die bewerkingen voor maken/bijwerken/verwijderen uitvoeren) die brongegevens wijzigt, gescheiden blijven van de code die alleen query's op de gegevens uitvoert.

Er is enige complexiteit betrokken bij het maken van een gegevensbron die de inhoud van het bestandssysteem nauwkeurig vertegenwoordigt en uitzonderingen correct afhandelt. In de voorbeelden in deze sectie wordt een verzameling momentopnamen gemaakt van FileInfo objecten die alle bestanden onder een opgegeven hoofdmap en alle bijbehorende submappen vertegenwoordigen. De werkelijke status van elk FileInfo item kan veranderen in de tijd tussen het begin en einde van het uitvoeren van een query. U kunt bijvoorbeeld een lijst maken met FileInfo objecten die u als gegevensbron wilt gebruiken. Als u de Length eigenschap in een query probeert te openen, probeert het FileInfo object toegang te krijgen tot het bestandssysteem om de waarde van Length. Als het bestand niet meer bestaat, krijgt u een FileNotFoundException in uw query, ook al voert u geen query's uit op het bestandssysteem.

Query's uitvoeren op bestanden met een opgegeven kenmerk of naam

In dit voorbeeld ziet u hoe u alle bestanden kunt vinden met een opgegeven bestandsnaamextensie (bijvoorbeeld '.txt') in een opgegeven mapstructuur. Ook ziet u hoe u het nieuwste of oudste bestand in de boomstructuur retourneert op basis van de aanmaaktijd. Mogelijk moet u de eerste regel van veel voorbeelden wijzigen, ongeacht of u deze code uitvoert op Windows, Mac of een Linux-systeem.

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}");

Bestanden groeperen op extensie

In dit voorbeeld ziet u hoe LINQ kan worden gebruikt om geavanceerde groeperings- en sorteerbewerkingen uit te voeren op lijsten met bestanden of mappen. Ook ziet u hoe u pagina-uitvoer in het consolevenster kunt weergeven met behulp van de Skip en Take methoden.

De volgende query laat zien hoe u de inhoud van een opgegeven mapstructuur kunt groeperen op basis van de bestandsnaamextensie.

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();
}

De uitvoer van dit programma kan lang zijn, afhankelijk van de details van het lokale bestandssysteem en waarop het startFolder is ingesteld. Als u het weergeven van alle resultaten wilt inschakelen, ziet u in dit voorbeeld hoe u door de resultaten kunt bladeren. Een geneste foreach lus is vereist omdat elke groep afzonderlijk wordt geïnventariseerd.

Een query uitvoeren op het totale aantal bytes in een set mappen

In dit voorbeeld ziet u hoe u het totale aantal bytes ophaalt dat wordt gebruikt door alle bestanden in een opgegeven map en alle bijbehorende submappen. De Sum methode voegt de waarden toe van alle items die in de select component zijn geselecteerd. U kunt deze query wijzigen om het grootste of kleinste bestand in de opgegeven mapstructuur op te halen door de Min of Max methode aan te roepen in plaats van Sum.

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 dit voorbeeld wordt het vorige voorbeeld uitgebreid om het volgende te doen:

  • De grootte ophalen in bytes van het grootste bestand.
  • De grootte ophalen in bytes van het kleinste bestand.
  • Het grootste of kleinste bestand van het FileInfo object ophalen uit een of meer mappen onder een opgegeven hoofdmap.
  • Een reeks ophalen, zoals de 10 grootste bestanden.
  • Bestanden rangschikken in groepen op basis van hun bestandsgrootte in bytes, waarbij bestanden worden genegeerd die kleiner zijn dan een opgegeven grootte.

Het volgende voorbeeld bevat vijf afzonderlijke query's die laten zien hoe u bestanden opvraagt en groepeert, afhankelijk van de bestandsgrootte in bytes. U kunt deze voorbeelden wijzigen om de query te baseren op een andere eigenschap van het FileInfo object.

// 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}");
    }
}

Als u een of meer volledige FileInfo objecten wilt retourneren, moet de query eerst elk object in de gegevensbron onderzoeken en vervolgens sorteren op de waarde van de eigenschap Length. Vervolgens kan de ene of de reeks met de grootste lengte worden geretourneerd. Gebruik First dit om het eerste element in een lijst te retourneren. Gebruik Take dit om het eerste n aantal elementen te retourneren. Geef een aflopende sorteervolgorde op om de kleinste elementen aan het begin van de lijst te plaatsen.

Query's uitvoeren op dubbele bestanden in een mapstructuur

Soms bevinden bestanden met dezelfde naam zich in meer dan één map. In dit voorbeeld ziet u hoe u een query uitvoert op dergelijke dubbele bestandsnamen onder een opgegeven hoofdmap. In het tweede voorbeeld ziet u hoe u query's kunt uitvoeren op bestanden waarvan de grootte en LastWrite-tijden ook overeenkomen.

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}");
    }   
}

De eerste query gebruikt een sleutel om een overeenkomst te bepalen. Er worden bestanden met dezelfde naam gevonden, maar waarvan de inhoud mogelijk anders is. De tweede query maakt gebruik van een samengestelde sleutel die overeenkomt met drie eigenschappen van het FileInfo object. Deze query is veel waarschijnlijker om bestanden te vinden met dezelfde naam en vergelijkbare of identieke inhoud.

    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}");
        }
    }
}

Een query uitvoeren op de inhoud van tekstbestanden in een map

In dit voorbeeld ziet u hoe u query's uitvoert op alle bestanden in een opgegeven mapstructuur, elk bestand opent en de inhoud ervan inspecteert. Dit type techniek kan worden gebruikt voor het maken van indexen of omgekeerde indexen van de inhoud van een mapstructuur. In dit voorbeeld wordt een eenvoudige tekenreekszoekopdracht uitgevoerd. Complexere typen patroonkoppelingen kunnen echter worden uitgevoerd met een reguliere expressie.

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);
}

De inhoud van twee mappen vergelijken

In dit voorbeeld ziet u drie manieren om twee bestandsvermeldingen te vergelijken:

  • Door een query uit te voeren op een Booleaanse waarde die aangeeft of de twee bestandslijsten identiek zijn.
  • Door een query uit te voeren op het snijpunt om de bestanden op te halen die zich in beide mappen bevinden.
  • Door een query uit te voeren op het ingestelde verschil om de bestanden op te halen die zich in de ene map bevinden, maar niet de andere.

De hier getoonde technieken kunnen worden aangepast om reeksen objecten van elk type te vergelijken.

De FileComparer klasse die hier wordt weergegeven, laat zien hoe u een aangepaste vergelijkingsklasse gebruikt in combinatie met de Standard-queryoperators. De klasse is niet bedoeld voor gebruik in praktijkscenario's. Hierbij wordt alleen de naam en lengte in bytes van elk bestand gebruikt om te bepalen of de inhoud van elke map identiek is of niet. In een praktijkscenario moet u deze vergelijkingsfunctie wijzigen om een strengere gelijkheidscontrole uit te voeren.

// 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);
    }
}

De volgorde van de velden van een bestand met scheidingstekens wijzigen

Een CSV-bestand (door komma's gescheiden waarden) is een tekstbestand dat vaak wordt gebruikt voor het opslaan van spreadsheetgegevens of andere tabelgegevens die worden vertegenwoordigd door rijen en kolommen. Door de Split methode te gebruiken om de velden te scheiden, is het eenvoudig om CSV-bestanden op te vragen en te manipuleren met behulp van LINQ. In feite kan dezelfde techniek worden gebruikt om de onderdelen van elke gestructureerde tekstregel opnieuw te ordenen; het is niet beperkt tot CSV-bestanden.

In het volgende voorbeeld wordt ervan uitgegaan dat de drie kolommen 'familienaam', 'voornaam' en 'id' van leerlingen/studenten vertegenwoordigen. De velden bevinden zich in alfabetische volgorde op basis van de familienamen van de leerlingen/studenten. De query produceert een nieuwe reeks waarin de id-kolom eerst wordt weergegeven, gevolgd door een tweede kolom waarin de voor- en familienaam van de leerling/student wordt gecombineerd. De regels worden opnieuw gerangschikt op basis van het id-veld. De resultaten worden opgeslagen in een nieuw bestand en de oorspronkelijke gegevens worden niet gewijzigd. In de volgende tekst ziet u de inhoud van het spreadsheet1.csv bestand dat in het volgende voorbeeld wordt gebruikt:

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

De volgende code leest het bronbestand en rangschikt elke kolom in het CSV-bestand opnieuw om de volgorde van de kolommen te wijzigen:

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
*/

Een bestand splitsen in veel bestanden met behulp van groepen

In dit voorbeeld ziet u een manier om de inhoud van twee bestanden samen te voegen en vervolgens een set nieuwe bestanden te maken waarmee de gegevens op een nieuwe manier worden ingedeeld. De query maakt gebruik van de inhoud van twee bestanden. De volgende tekst toont de inhoud van het eerste bestand, names1.txt:

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

Het tweede bestand, names2.txt, bevat een andere set namen, waarvan sommige gemeen zijn met de eerste set:

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

De volgende code voert een query uit op beide bestanden, neemt de samenvoeging van beide bestanden en schrijft vervolgens een nieuw bestand voor elke groep, gedefinieerd door de eerste letter van de familienaam:

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
 */

Inhoud van niet-imilare bestanden samenvoegen

In dit voorbeeld ziet u hoe u gegevens uit twee door komma's gescheiden bestanden koppelt die een gemeenschappelijke waarde delen die wordt gebruikt als een overeenkomende sleutel. Deze techniek kan handig zijn als u gegevens uit twee spreadsheets moet combineren, of uit een spreadsheet en uit een bestand met een andere indeling, in een nieuw bestand. U kunt het voorbeeld aanpassen om te werken met elk type gestructureerde tekst.

In de volgende tekst ziet u de inhoud van scores.csv. Het bestand vertegenwoordigt spreadsheetgegevens. Kolom 1 is de id van de student en de kolommen 2 tot en met 5 zijn testscores.

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

In de volgende tekst ziet u de inhoud van names.csv. Het bestand vertegenwoordigt een spreadsheet met de familienaam, voornaam en student-id van de leerling/student.

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

Voeg inhoud toe van niet-imilare bestanden die gerelateerde informatie bevatten. Bestand names.csv bevat de naam van de leerling/student plus een id-nummer. Bestand scores.csv bevat de id en een set van vier testscores. De volgende query voegt de scores toe aan de namen van leerlingen/studenten met behulp van id als overeenkomende sleutel. De code wordt weergegeven in het volgende voorbeeld:

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("{0} total names in list", scoreQuery.Count());
/* 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
 */

Kolomwaarden berekenen in een CSV-tekstbestand

In dit voorbeeld ziet u hoe u statistische berekeningen uitvoert, zoals Som, Gemiddelde, Min en Max op de kolommen van een .csv-bestand. De voorbeeldprincipes die hier worden weergegeven, kunnen worden toegepast op andere typen gestructureerde tekst.

In de volgende tekst ziet u de inhoud van scores.csv. Stel dat de eerste kolom een student-id vertegenwoordigt en de volgende kolommen scores van vier examens vertegenwoordigen.

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

In de volgende tekst ziet u hoe u de Split methode gebruikt om elke tekstregel te converteren naar een matrix. Elk matrixelement vertegenwoordigt een kolom. Ten slotte wordt de tekst in elke kolom geconverteerd naar de numerieke weergave.

public class SumColumns
{
    public static void SumCSVColumns(string fileName)
    {
        string[] lines = File.ReadAllLines(fileName);

        // Specifies the column to compute.
        int exam = 3;

        // Spreadsheet format:
        // Student ID    Exam#1  Exam#2  Exam#3  Exam#4
        // 111,          97,     92,     81,     60

        // Add one to exam to skip over the first column,
        // which holds the student ID.
        SingleColumn(lines, exam + 1);
        Console.WriteLine();
        MultiColumns(lines);
    }

    static void SingleColumn(IEnumerable<string> strs, int examNum)
    {
        Console.WriteLine("Single Column Query:");

        // Parameter examNum specifies the column to
        // run the calculations on. This value could be
        // passed in dynamically at run time.

        // Variable columnQuery is an IEnumerable<int>.
        // The following query performs two steps:
        // 1) use Split to break each row (a string) into an array
        //    of strings,
        // 2) convert the element at position examNum to an int
        //    and select it.
        var columnQuery = from line in strs
                          let elements = line.Split(',')
                          select Convert.ToInt32(elements[examNum]);

        // Execute the query and cache the results to improve
        // performance. This is helpful only with very large files.
        var results = columnQuery.ToList();

        // Perform aggregate calculations Average, Max, and
        // Min on the column specified by examNum.
        double average = results.Average();
        int max = results.Max();
        int min = results.Min();

        Console.WriteLine($"Exam #{examNum}: Average:{average:##.##} High Score:{max} Low Score:{min}");
    }

    static void MultiColumns(IEnumerable<string> strs)
    {
        Console.WriteLine("Multi Column Query:");

        // Create a query, multiColQuery. Explicit typing is used
        // to make clear that, when executed, multiColQuery produces
        // nested sequences. However, you get the same results by
        // using 'var'.

        // The multiColQuery query performs the following steps:
        // 1) use Split to break each row (a string) into an array
        //    of strings,
        // 2) use Skip to skip the "Student ID" column, and store the
        //    rest of the row in scores.
        // 3) convert each score in the current row from a string to
        //    an int, and select that entire sequence as one row
        //    in the results.
        var multiColQuery = from line in strs
                            let elements = line.Split(',')
                            let scores = elements.Skip(1)
                            select (from str in scores
                                    select Convert.ToInt32(str));

        // Execute the query and cache the results to improve
        // performance.
        // ToArray could be used instead of ToList.
        var results = multiColQuery.ToList();

        // Find out how many columns you have in results.
        int columnCount = results[0].Count();

        // Perform aggregate calculations Average, Max, and
        // Min on each column.
        // Perform one iteration of the loop for each column
        // of scores.
        // You can use a for loop instead of a foreach loop
        // because you already executed the multiColQuery
        // query by calling ToList.
        for (int column = 0; column < columnCount; column++)
        {
            var results2 = from row in results
                           select row.ElementAt(column);
            double average = results2.Average();
            int max = results2.Max();
            int min = results2.Min();

            // Add one to column because the first exam is Exam #1,
            // not Exam #0.
            Console.WriteLine($"Exam #{column + 1} Average: {average:##.##} High Score: {max} Low Score: {min}");
        }
    }
}
/* Output:
    Single Column Query:
    Exam #4: Average:76.92 High Score:94 Low Score:39

    Multi Column Query:
    Exam #1 Average: 86.08 High Score: 99 Low Score: 35
    Exam #2 Average: 86.42 High Score: 94 Low Score: 72
    Exam #3 Average: 84.75 High Score: 91 Low Score: 65
    Exam #4 Average: 76.92 High Score: 94 Low Score: 39
 */

Als uw bestand een bestand met tabs is, werkt u het argument in de Split methode bij naar \t.