Jak znaleźć najnowszy plik w katalogu za pomocą .NET i bez zapętlania?

142

Muszę znaleźć ostatnio zmodyfikowany plik w katalogu.

Wiem, że mogę przeglądać każdy plik w folderze i porównywać File.GetLastWriteTime, ale czy jest lepszy sposób na zrobienie tego bez zapętlania?

Chris Klepeis
źródło
18
Nie, nie ma lepszego sposobu na uniknięcie zapętlenia. Nawet użycie LINQ po prostu ukrywa pętlę w jakiejś głębszej funkcjonalności, w której nie można jej zobaczyć bezpośrednio.
Oliver
1
Jeśli chcesz znaleźć ostatnio zmodyfikowane pliki w całym systemie plików, przydatny byłby dziennik zmian NTFS. Jednak bardzo trudny w użyciu w C #.
Ben Voigt,

Odpowiedzi:

318

co powiesz na coś takiego ...

var directory = new DirectoryInfo("C:\\MyDirectory");
var myFile = (from f in directory.GetFiles()
             orderby f.LastWriteTime descending
             select f).First();

// or...
var myFile = directory.GetFiles()
             .OrderByDescending(f => f.LastWriteTime)
             .First();
Scott Ivey
źródło
84
Osobiście uważam, że wersja bez cukru jest łatwiejsza do odczytania:directory.GetFiles().OrderByDescending(f => f.LastWriteTime).First()
Jørn Schou-Rode
6
tak, w większości przypadków też się z tym zgadzam - ale podając przykłady składnia zapytania sprawia, że ​​jest to nieco bardziej oczywiste, że jest to zapytanie linq. Zaktualizuję przykład z obiema opcjami, aby wyjaśnić.
Scott Ivey
3
Dzięki! Teraz muszę tylko przekonać szefa, aby przyspieszył proces aktualizacji z .net 2.0, abym mógł używać Linq :)
Chris Klepeis
3
możesz używać linq z 2.0 SP1 z odrobiną dodatkowej pracy - po prostu odwołaj się do pliku System.Core.dll z 3.5 i ustaw go na „kopiuj lokalnie”
Scott Ivey
8
Czy nie powinno to używać FirstOrDefault()zamiast First()? Ta ostatnia spowoduje InvalidOperationException, że katalog jest pusty.
Tobias J
20

Jeśli chcesz wyszukiwać rekurencyjnie, możesz użyć tego pięknego fragmentu kodu:

public static FileInfo GetNewestFile(DirectoryInfo directory) {
   return directory.GetFiles()
       .Union(directory.GetDirectories().Select(d => GetNewestFile(d)))
       .OrderByDescending(f => (f == null ? DateTime.MinValue : f.LastWriteTime))
       .FirstOrDefault();                        
}

Po prostu nazwij to w następujący sposób:

FileInfo newestFile = GetNewestFile(new DirectoryInfo(@"C:\directory\"));

i to wszystko. Zwraca FileInfoinstancję lub nulljeśli katalog jest pusty.

Edgar Villegas Alvarado
źródło
12
Możesz też skorzystać z opcji wyszukiwania rekurencyjnego .
ricksmt
17

Rozwijając pierwszy z powyższych, jeśli chcesz wyszukać określony wzorzec, możesz użyć następującego kodu:

string pattern = "*.txt";
var dirInfo = new DirectoryInfo(directory);
var file = (from f in dirInfo.GetFiles(pattern) orderby f.LastWriteTime descending select f).First();
Zamir
źródło
Potrzebowałem tego. Dziękuję Ci.
Ashilon
10

Wersja inna niż LINQ:

/// <summary>
/// Returns latest writen file from the specified directory.
/// If the directory does not exist or doesn't contain any file, DateTime.MinValue is returned.
/// </summary>
/// <param name="directoryInfo">Path of the directory that needs to be scanned</param>
/// <returns></returns>
private static DateTime GetLatestWriteTimeFromFileInDirectory(DirectoryInfo directoryInfo)
{
    if (directoryInfo == null || !directoryInfo.Exists)
        return DateTime.MinValue;

    FileInfo[] files = directoryInfo.GetFiles();
    DateTime lastWrite = DateTime.MinValue;

    foreach (FileInfo file in files)
    {
        if (file.LastWriteTime > lastWrite)
        {
            lastWrite = file.LastWriteTime;
        }
    }

    return lastWrite;
}

/// <summary>
/// Returns file's latest writen timestamp from the specified directory.
/// If the directory does not exist or doesn't contain any file, null is returned.
/// </summary>
/// <param name="directoryInfo">Path of the directory that needs to be scanned</param>
/// <returns></returns>
private static FileInfo GetLatestWritenFileFileInDirectory(DirectoryInfo directoryInfo)
{
    if (directoryInfo == null || !directoryInfo.Exists)
        return null;

    FileInfo[] files = directoryInfo.GetFiles();
    DateTime lastWrite = DateTime.MinValue;
    FileInfo lastWritenFile = null;

    foreach (FileInfo file in files)
    {
        if (file.LastWriteTime > lastWrite)
        {
            lastWrite = file.LastWriteTime;
            lastWritenFile = file;
        }
    }
    return lastWritenFile;
}
Timothy P.
źródło
1
Przepraszamy, nie widziałem faktu, że nie chcesz zapętlić. W każdym razie ... może to pomoże komuś w szukaniu czegoś podobnego
TimothyP
3
Ten kod nie kompiluje się. - lastUpdatedFile nie powinien być tablicą. - Początkowa wartość lastUpdate jest nieprawidłowa (0001/0/0).
Lars A. Brekken
4

Krótko i prosto :

new DirectoryInfo(path).GetFiles().OrderByDescending(o => o.LastWriteTime).FirstOrDefault();
Jakub
źródło
3

jest trochę późno, ale ...

Twój kod nie będzie działał, ponieważ list<FileInfo> lastUpdateFile = null; i później lastUpdatedFile.Add(file);zostanie zgłoszony wyjątek NullReference. Wersja robocza powinna być:

private List<FileInfo> GetLastUpdatedFileInDirectory(DirectoryInfo directoryInfo)
{
    FileInfo[] files = directoryInfo.GetFiles();
    List<FileInfo> lastUpdatedFile = new List<FileInfo>();
    DateTime lastUpdate = DateTime.MinValue;
    foreach (FileInfo file in files)
    {
        if (file.LastAccessTime > lastUpdate)
        {
            lastUpdatedFile.Add(file);
            lastUpdate = file.LastAccessTime;
        }
    }

    return lastUpdatedFile;
}

Dzięki

Oleg Karbushev
źródło
2

Możesz reagować na nową aktywność plików za pomocą FileSystemWatcher .

Scott Marlowe
źródło
1
To nie działa, ponieważ plik można modyfikować, gdy jego aplikacja nie jest uruchomiona.
Francis B.
1
nie podał takich szczegółów ... Skąd wiemy, że to nie jest trwała aplikacja?
Scott Marlowe
1
Nie mamy, ale Scott ma lepsze rozwiązanie, które działa w obu przypadkach.
Badaro,
Należy również pamiętać, że FSW nie będzie działać z większością udostępnionych folderów sieciowych.
bkqc
2

Inne podejście, jeśli używasz Directory.EnumerateFilesi chcesz czytać pliki w najnowszej modyfikacji przez pierwsze.

foreach (string file in Directory.EnumerateFiles(fileDirectory, fileType).OrderByDescending(f => new FileInfo(f).LastWriteTime))

}
Gaurravs
źródło
1

Oto wersja, która pobiera najnowszy plik z każdego podkatalogu

List<string> reports = new List<string>();    
DirectoryInfo directory = new DirectoryInfo(ReportsRoot);
directory.GetFiles("*.xlsx", SearchOption.AllDirectories).GroupBy(fl => fl.DirectoryName)
.ForEach(g => reports.Add(g.OrderByDescending(fi => fi.LastWriteTime).First().FullName));
Michael Bahig
źródło
0
private List<FileInfo> GetLastUpdatedFileInDirectory(DirectoryInfo directoryInfo)
{
    FileInfo[] files = directoryInfo.GetFiles();
    List<FileInfo> lastUpdatedFile = null;
    DateTime lastUpdate = new DateTime(1, 0, 0);
    foreach (FileInfo file in files)
    {
        if (file.LastAccessTime > lastUpdate)
        {
            lastUpdatedFile.Add(file);
            lastUpdate = file.LastAccessTime;
        }
    }

    return lastUpdatedFile;
}
Sylver1981
źródło
0

Robię to z kilku moich aplikacji i używam następującego oświadczenia:

  var inputDirectory = new DirectoryInfo("\\Directory_Path_here");
  var myFile = inputDirectory.GetFiles().OrderByDescending(f => f.LastWriteTime).First();

Stąd będziesz mieć nazwę pliku dla ostatnio zapisanego / dodanego / zaktualizowanego pliku w katalogu zmiennej "inputDirectory". Teraz możesz uzyskać do niego dostęp i robić z nim, co chcesz.

Mam nadzieję, że to pomoże.

JasonR
źródło
Aby to dodać, jeśli Twoim celem jest odzyskanie ścieżki pliku i używasz FirstOrDefault, ponieważ możliwe jest, że nie ma żadnych wyników, możesz użyć operatora warunkowego zerowego we właściwości FullName. Spowoduje to zwrócenie „null” dla twojej ścieżki bez konieczności zapisywania FileInfo z FirstOrDefault przed wywołaniem FullName. string path = new DirectoryInfo (path) .GetFiles (). OrderByDescending (o => o.LastWriteTime) .FirstOrDefault () ?. FullName;
StalePhish