Gå til indholdet

Synkron filhåndtering

Filhåndtering i C# er afgørende for mange applikationer, da det giver mulighed for at læse, skrive og manipulere filer og mapper på disken. C# indeholder flere nyttige klasser til filhåndtering, herunder File, Directory, FileInfo og DirectoryInfo.

Information til undervisere

I den grundlæggende form behøver kursterne ikke at gå i dybden med filhåndtering - de skal blot kende til de mest almindelige metoder og klasser. I den mere avancede form kan man tage fat i Streams og yield-keywordet.

Se også Async IO for en mere avanceret gennemgang af filhåndtering.

File og Directory

File og Directory-klasserne er en del af System.IO-navnerummet og indeholder statiske metoder til at arbejde med filer og mapper. Nogle almindelige metoder inkluderer:

  • File.Exists(string)
  • File.ReadAllText(string)
  • File.WriteAllText(string, string)
  • Directory.Exists(string)
  • Directory.CreateDirectory(string)

Eksempel på brug af File og Directory

string filePath = "example.txt";
string folderPath = @"c:\temp";

if (!File.Exists(filePath))
{
    File.WriteAllText(filePath, "Hello, world!");
}

if (!Directory.Exists(folderPath))
{
    Directory.CreateDirectory(folderPath);
}

FileInfo og DirectoryInfo

FileInfo og DirectoryInfo-klasserne er også en del af System.IO-navnerummet. Disse klasser repræsenterer filer og mapper som objekter og indeholder metoder og egenskaber til at arbejde med dem. Nogle almindelige metoder og egenskaber inkluderer:

  • FileInfo.Length
  • FileInfo.FullName
  • FileInfo.Delete()
  • DirectoryInfo.GetFiles()
  • DirectoryInfo.GetDirectories()
  • DirectoryInfo.FullName

Eksempel på brug af FileInfo og DirectoryInfo

FileInfo fileInfo = new FileInfo("example.txt");
DirectoryInfo directoryInfo = new DirectoryInfo(@"c:\temp");

long fileSize = fileInfo.Length;
var files = directoryInfo.GetFiles();

Path

Path-klassen, der også er en del af System.IO-navnerummet, indeholder statiske metoder til at arbejde med filstier. Den hjælper med at konstruere stier på en platformuafhængig måde og til at hente information om stier, som f.eks. filnavn, mappenavn eller filudvidelse. Nogle af de mest anvendte metoder i Path-klassen inkluderer:

  • Path.Combine(string, string)
  • Path.GetFileName(string)
  • Path.GetDirectoryName(string)
  • Path.GetExtension(string)
  • Path.GetRandomFileName()

Eksempel på brug af Path

string folderPath = @"c:\temp";
string fileName = "example.txt";
string fullPath = Path.Combine(folderPath, fileName);

Console.WriteLine($"Fuld sti: {fullPath}");
Console.WriteLine($"Filnavn: {Path.GetFileName(fullPath)}");
Console.WriteLine($"Mappenavn: {Path.GetDirectoryName(fullPath)}");
Console.WriteLine($"Filudvidelse: {Path.GetExtension(fullPath)}");

// Opretter en sti til en ny fil i temp-mappen med en unik navn
string tempFilePath = Path.Combine(folderPath, Path.GetRandomFileName());
Console.WriteLine($"Sti til ny temp fil: {tempFilePath}");

I dette eksempel bruges Path.Combine til sikkert at konstruere en fuld sti til en fil ved at kombinere stien til en mappe med et filnavn. Derefter bruges andre Path-metoder til at hente forskellige dele af stien, såsom filnavn, mappenavn og filudvidelse. Path.GetRandomFileName() genererer et tilfældigt filnavn, hvilket er nyttigt for at oprette midlertidige filer uden risiko for navnesammenfald.

Path-klassen er særligt nyttig, når man arbejder med filsystemer, fordi den håndterer forskellene i filsystemets stistrenge på forskellige operativsystemer. Dette gør kode mere bærbar og reducerer risikoen for fejl ved stihåndtering.

Opgaver

Streams

Streams er en vigtig del af filhåndtering i C#, da de giver en abstraktion til læsning og skrivning af data. System.IO.Stream er en abstrakt klasse, der repræsenterer en sekvens af bytes, og det kan bruges til at arbejde med forskellige typer af datakilder, såsom filer, netværksforbindelser og hukommelse.

Nogle almindelige typer af streams inkluderer FileStream, MemoryStream og NetworkStream. For at arbejde med streams, kan metoder som Read, Write, Flush og Close anvendes. Det er vigtigt at frigive ressourcerne korrekt, når man arbejder med streams, hvilket kan gøres ved hjælp af using-blokken.

Der findes også en StreamReader og StreamWriter-klasse, der bygger på Stream-klassen og giver mulighed for at læse og skrive tekstdata fra og til en stream. Den kan du se som en hjælperklasse, der gør det nemmere at arbejde med tekstdata.

Streamreader og Streamwriter

StreamReader og StreamWriter-klasserne er en del af System.IO-navnerummet og bruges til at læse og skrive tekstdata fra og til en stream. De kan bruges til at læse og skrive tekstfiler, hukommelsesstreams og netværksstreams. Nogle almindelige metoder inkluderer:

  • StreamReader.ReadLine()
  • StreamReader.ReadToEnd()
  • StreamWriter.WriteLine(string)
  • StreamWriter.Write(string)
  • StreamWriter.Flush()
  • StreamWriter.Close()
  • StreamReader.Close()

Eksempler med StreamReader og StreamWriter

Her er et eksempel på, hvordan man kan bruge StreamReader til at læse fra en fil, og StreamWriter til at skrive til en fil på en mere simpel måde:

string filePath = "example.txt";
string textToWrite = "Hello, world!";

// Skrive tekst til en fil
using (StreamWriter writer = new StreamWriter(filePath))
{
    writer.WriteLine(textToWrite);
}

// Læse tekst fra en fil
using (StreamReader reader = new StreamReader(filePath))
{
    string textRead = reader.ReadToEnd();
    Console.WriteLine(textRead);
}

I dette eksempel bruger StreamWriter-klassen til at skrive en streng til en fil, og StreamReader-klassen bruges til at læse indholdet af filen tilbage som en streng. using-blokken sikrer, at filressourcerne lukkes korrekt efter brug, hvilket hjælper med at forhindre ressource lækager. Dette er en mere direkte og lettilgængelig måde at arbejde med filer på, når man arbejder med tekstindhold.

Denne tilgang er især praktisk for nybegyndere eller i scenarier, hvor der primært arbejdes med tekstfiler, og hvor der ikke er behov for den lavniveau kontrol, som FileStream tilbyder.

Her er et eksempel på, hvordan man kan bruge StreamReader.ReadLine() til at læse en fil linje for linje:

string filePath = "example.txt";

using (StreamReader reader = new StreamReader(filePath))
{
    string line;
    while ((line = reader.ReadLine()) != null)
    {
        Console.WriteLine(line);
    }
}

I dette eksempel oprettes en instans af StreamReader for at åbne filen example.txt. Derefter bruges et while-loop til at læse hver linje fra filen en ad gangen ved hjælp af ReadLine()-metoden. Hver læst linje udskrives til konsollen, indtil der ikke er flere linjer i filen. Dette mønster er meget almindeligt og nyttigt i mange scenarier, hvor tekstfiler skal behandles linje for linje.

FileStream

FileStream-klassen er en del af System.IO-navnerummet og bruges til at læse og skrive rå byte-data til og fra en fil. Den giver en mere lavniveau adgang til filsystemet og er nyttig, når der er behov for mere kontrol over læsning og skrivning af data.

Eksempel med FileStream

Her er et simpelt eksempel på, hvordan man bruger en FileStream til at læse og skrive data til en fil:

string filePath = "example.txt";
byte[] dataToWrite = Encoding.UTF8.GetBytes("Hello, world!");
byte[] dataReadBuffer = new byte[1024];

// Skrive data til en fil
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
    fileStream.Write(dataToWrite, 0, dataToWrite.Length);
}

// Læse data fra en fil
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
    int bytesRead = fileStream.Read(dataReadBuffer, 0, dataReadBuffer.Length);
    string dataRead = Encoding.UTF8.GetString(dataReadBuffer, 0, bytesRead);
    Console.WriteLine(dataRead);
}

I dette eksempel oprettes der en FileStream til at skrive data til en fil og en anden FileStream til at læse data fra den samme fil. using-blokken sikrer, at ressourcerne frigives korrekt, når arbejdet med filen er afsluttet.

Info

Se også Async IO for en mere avanceret gennemgang af filhåndtering.

Opgaver

Brug af yield

yield keywordet er en kraftfuld del af C#, der tillader en metode at returnere en sekvens af værdier til en iterator en ad gangen, frem for at returnere alle værdierne på én gang. Dette er særligt nyttigt i filhåndtering, når du arbejder med store filer eller søger efter specifikke data i en fil.

Forestil dig, at du har en logfil, hvor visse linjer indeholder en fejlkode markeret med et #-tegn. Du ønsker at læse filen og behandle kun de linjer, der indeholder dette tegn. Ved hjælp af yield kan du oprette en metode, der returnerer linjer med det specificerede tegn en ad gangen, hvilket gør det muligt at behandle dem dynamisk:

public static IEnumerable<string> LæsLinjerMedTegn(string filsti, char tegn)
{
    using (var reader = new StreamReader(filsti))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            if (line.Contains(tegn))
            {
                yield return line;
            }
        }
    }
}

Denne metode benytter StreamReader til at læse filen linje for linje. For hver linje undersøges det, om den indeholder det specificerede tegn (i dette tilfælde #). Hvis en linje indeholder tegnet, returneres den enkeltvis til forbrugeren ved hjælp af yield return.

For at anvende denne metode kan du gøre følgende:

string filsti = @"sti/til/din/logfil.txt";

foreach (var linje in LæsLinjerMedTegn(filsti, '#'))
{
    Console.WriteLine(linje);
}

Dette vil iterere over hver linje i filen, der opfylder betingelsen, og udskrive den til konsollen.

Der er flere fordele ved at bruge yield i filoperationer:

  • Effektivitet: Ved at bruge yield, undgår du at indlæse hele filens indhold i hukommelsen på én gang. Dette er særlig nyttigt for behandling af store filer.
  • Fleksibilitet: Metoden tillader dynamisk filtrering og bearbejdning af filindhold, hvilket giver en høj grad af kontrol over, hvilke data der behandles.
  • Læsbarhed: Koden bliver mere læsbar og lettere at vedligeholde, da logikken for datafiltrering og iteration er strømlinet og nem at forstå.

Integrationen af yield i synkrone filoperationer demonstrerer C#’s kraftfulde funktioner for at håndtere filer og datastrømme på en effektiv og fleksibel måde. Ved at udnytte denne teknik kan udviklere skabe mere effektive og responsivrige applikationer, der effektivt kan håndtere fildata.