Comportamento anomalo metodo Directory.GetFiles

lunedì 16 giugno 2014 - 15.41
Tag Elenco Tags  C#  |  .NET 2.0

AntCiar Profilo | Expert

Salve a tutti.

Ho riscontrato a mio parere un comportamento anomalo del metodo 'Directory.GetFiles'.
In pratica succede questo:

in una cartella ho un solo file chiamato pippo.txtold

richiamando il metodo Directory.GetFiles(cartella, "*.txt") mi da come risultato il file in questione (comportamento inaspettato).
A questo punto ho pensato che nel confronto il metodo utilizzasse una sorta di StartWith per il confronto.

per verifica ho fatto Directory.GetFiles(cartella, "*.txto") e in questo caso non mi da niente come risultato.
stessa cosa (nessun risultato) anche per Directory.GetFiles(cartella, "*.txtol").

per Directory.GetFiles(cartella, "*.txtold") invece mi da come risultato il file (cosa che mi aspettavo)

Non so a questo punto se il comportamento del Directory.GetFiles è corretto oppure ho scovato un bel BUG...

Cristian Barca

0v3rCl0ck Profilo | Guru

Decompilando un po' il framework (con JustDecompile), ho controllato lo sviluppo della ricerca, e ho trovato queste cose (semplificando):

prima di tutto viene fatta una normalizzazione del search pattern:

private static string NormalizeSearchPattern(string searchPattern) { string str = searchPattern.TrimEnd(Path.TrimEndChars); if (str.Equals(".")) { str = "*"; } Path.CheckSearchPattern(str); return str; }

poi viene ricavato un fullpath concatenando il path con il search pattern:

private static string GetFullSearchString(string fullPath, string searchPattern) { string str = Path.InternalCombine(fullPath, searchPattern); char chr = str[str.Length - 1]; if (Path.IsDirectorySeparator(chr) || chr == Path.VolumeSeparatorChar) { str = string.Concat(str, '*'); } return str; }

infine viene passato tutto ad un API di windows:

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

(e al suo socio: FindNextFile)

che si può vedere nella prima init che viene fatta nel metodo CommonInit():

string str = Path.InternalCombine(this.searchData.fullPath, this.searchCriteria); Win32Native.WIN32_FIND_DATA wIN32FINDDATum = new Win32Native.WIN32_FIND_DATA(); this._hnd = Win32Native.FindFirstFile(str, wIN32FINDDATum);

quindi se ad esempio chiamiamo la GetFiles così:

Directory.GetFiles(@"C:\Temp", @"*.txt")

negli internals dovrebbe essere convertito in una chiamata di sistema così:

Win32Native.FindFirstFile("C:\Temp\*.txt", wIN32FINDDATum);

dato questo ho provato a riprodurre il codice del framework chiamando direttamente le API, e i risultati sono consistenti, se cerco "*.txt" mi ritornano entrambi i file "pippo.txt" e "pippo.txtold", se cerco "*.txto" non mi ritorna niente, e se cerco "*.txtold" mi ritorna soltanto "pippo.txtold":

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

questo codice gira su LINQPad (http://www.linqpad.net/), se vuoi farlo girare in una console, togli i .Dump() e metti dei bei Console.WriteLine(...)

Conclusione: non saprei dirti se questo è un BUG o un comportamento voluto o quanto meno riconosciuto, ma ad ogni modo il framework non c'entra, se è un bug, hai trovato addirittura un bug nella api di windows

In effetti provando sul windows explorer ottengo questo risultato discrepante dalle API:


726x205 10Kb



731x168 8Kb



725x167 8Kb


Quindi usano un altra api windows? oppure applicano una diversa normalizzazione sul search pattern (dato che è responsabilità del chiamante creare un path di ricerca corretto)? più facile sia la seconda che ho detto.


Michael Denny | Visual C# MVP
http://blogs.dotnethell.it/Regulator/
http://dennymichael.wordpress.com
http://mvp.microsoft.com/mvp/Michael%20Denny-5000735
Twitter: @dennymic

0v3rCl0ck Profilo | Guru

Ti allego anche l'indirizzo dove puoi trovare tutta la documentazione sulle api windows, e gli esempi per utilizzarle:

http://www.pinvoke.net/default.aspx/kernel32/findfirstfile.html



Michael Denny | Visual C# MVP
http://blogs.dotnethell.it/Regulator/
http://dennymichael.wordpress.com
http://mvp.microsoft.com/mvp/Michael%20Denny-5000735
Twitter: @dennymic

0v3rCl0ck Profilo | Guru

... e a proposito... tutto bene dalle vostre parti?

con azure è uno spasso o una dannazione?


Michael Denny | Visual C# MVP
http://blogs.dotnethell.it/Regulator/
http://dennymichael.wordpress.com
http://mvp.microsoft.com/mvp/Michael%20Denny-5000735
Twitter: @dennymic

AntCiar Profilo | Expert

Ciao Michael.

Qui tutto bene, o almeno sembra.
Con azure pare essere uno spasso anche se non sono io direttamente ad interessarmene.
Ogni tanto il mio collega lo sento un po bestemmiare però tutto sommato è ok.

per quanto riguarda il GetFiles darò una occhiata ai link che mi hai inviato.

saluti a te e Alessando.
Cristian Barca

0v3rCl0ck Profilo | Guru

>Ciao Michael.
>
>Qui tutto bene, o almeno sembra.

hehe, bene bene :)

>Con azure pare essere uno spasso anche se non sono io direttamente
>ad interessarmene.
>Ogni tanto il mio collega lo sento un po bestemmiare però tutto
>sommato è ok.

beh tutto normale, come nell'informatica in generale... diciamo che con azure, non ci si annoia mai, quasi ogni settimana c'è qualcosa di nuovo, a volte penso di essere pazzo, aggiorno il pannello e il wizard di creazione virtual non è più lo stesso, aggiorno powershell e mi spariscono degli switch, poi vado qui http://azure.microsoft.com/en-us/updates/ e tutto mi è chiaro

>
>per quanto riguarda il GetFiles darò una occhiata ai link che
>mi hai inviato.

si alla fine per come è implementato quel metodo su .net è così, e c'è poco da fare...anzi ho trovato la spiegazione completa sull'msdn http://msdn.microsoft.com/en-us/library/ms143316.aspx

When you use the asterisk wildcard character in a searchPattern such as "*.txt", the number of characters in the specified extension affects the search as follows:
- If the specified extension is exactly three characters long, the method returns files with extensions that begin with the specified extension. For example, "*.xls" returns both "book.xls" and "book.xlsx".
- In all other cases, the method returns files that exactly match the specified extension. For example, "*.ai" returns "file.ai" but not "file.aif".
When you use the question mark wildcard character, this method returns only files that match the specified file extension. For example, given two files, "file1.txt" and "file1.txtother", in a directory, a search pattern of "file?.txt" returns just the first file, whereas a search pattern of "file*.txt" returns both files.

Because this method checks against file names with both the 8.3 file name format and the long file name format, a search pattern similar to "*1*.txt" may return unexpected file names. For example, using a search pattern of "*1*.txt" returns "longfilename.txt" because the equivalent 8.3 file name format is "LONGFI~1.TXT".

Quindi per cambiare quel comportamento è necessario arricchire il filtro manualmente tenendo conto di quelle regole, e quindi ho cercato quale fosse il metodo migliore, e ho trovato EnumerateFiles (http://msdn.microsoft.com/en-us/library/dd383571.aspx), che è l'alter-ego di GetFiles, ma che invece che valutare subito tutti i files e ritornare una collezione completa, ritorna un enumeratore lazy, che rilascia la palla a te per ogni file trovato, in questo modo linq query come .Any() .Take() ecc... sono più performanti:

The EnumerateFiles and GetFiles methods differ as follows: When you use EnumerateFiles, you can start enumerating the collection of names before the whole collection is returned; when you use GetFiles, you must wait for the whole array of names to be returned before you can access the array. Therefore, when you are working with many files and directories, EnumerateFiles can be more efficient.

Detto questo come soluzione io adotterei una cosa di questo tipo:

Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

nel tuo caso se davvero vuoi cercare tutti i files senza usare metodi come Take o Any, non cambia molto da fare la stessa cosa con GetFiles, perchè di fatto la directory viene completamente enumerata per scoprire tutti i file con quell'estensione. Tieni presente che passo comunque il searchpattern, anche se solo con l'EndsWith la cosa funzionerebbe, perchè a sua volta viene passata all'api di sistema, sperando in un ottimizzazione da parte del sistema operativo in particolare spero vengano sfruttati gli indici del filesystem, inoltre tieni anche conto che la funzione per calcolare l'estensione non è così banale come un EndsWith, ma è piuttosto una cosa del genere:

(presa direttamente dal decompilato della proprietà FileSystemInfo.Extension)
Il codice sorgente non è stato renderizzato qui
perchè non c'è sufficiente spazio.
Clicca qui per visualizzarlo in una nuova finestra

quindi in sostanza, meglio far fare a .net una scrematura iniziale, e poi applicare funzioni semplici per l'ulteriore affinamento della query.


>
>saluti a te e Alessando.
>Cristian Barca

salutaci tutta la combriccola li
Michael Denny | Visual C# MVP
http://blogs.dotnethell.it/Regulator/
http://dennymichael.wordpress.com
http://mvp.microsoft.com/mvp/Michael%20Denny-5000735
Twitter: @dennymic
Partecipa anche tu! Registrati!
Hai bisogno di aiuto ?
Perchè non ti registri subito?

Dopo esserti registrato potrai chiedere
aiuto sul nostro Forum oppure aiutare gli altri

Consulta le Stanze disponibili.

Registrati ora !
Copyright © dotNetHell.it 2002-2025
Running on Windows Server 2008 R2 Standard, SQL Server 2012 & ASP.NET 3.5