[LINQ] Trouver le nom de fichier suivant disponible

This article is available in english.

Parfois, les exemples les plus simples sont les meilleurs.

Mettons que vous avez un fichier de configuration, et que vous voulez en faire une copie avant de le modifier. Facile, vous copiez le fichier en “filename.bak”. Mais que se passe-t-il si ce fichier existe déjà ? Soit vous l’écrasez, soit vous créez un nom de fichier auto-incrémenté.

Si l’on veut faire ce dernier, il est possible de le faire avec une boucle for. Mais comme vous êtes un bon programmeur fonctionnel, vous allez le faire en utilisant LINQ.

On peut donc écrire ceci :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public static string CreateNewFileName(string filePath)
{
if (!File.Exists(filePath))
return filePath;

// On évite de faire cela pour tous les fichiers
var name = Path.GetFileNameWithoutExtension(filePath);
var extension = Path.GetExtension(filePath);

// Maintenant on cherche le fichier suivant
var fileQuery = from index in Enumerable.Range(2, 10000)

// On construit le nom
let fileName = string.Format("{0} ({1}){2}", name, index, extension)

// Est-ce que le fichier existe ?
where !File.Exists(fileName)

// Non ? On le sélectionne.
select fileName;

// On retourne le premier.
return fileQuery.First();
}

Il faut noter l'utilisation du mot clé “let” qui permet de réutiliser ce que l’on appelle une “range variable”. Dans ce cas, cela permet d’éviter d’appeler string.Format plusieurs fois.

 

Le cas de l’infini

Il y a malgré tout un petit problème dans cette implémentation, qui prend la forme du “10000” arbitraire. Cela peut-être tout à fait correct si vous n’avez pas l’intention de faire 10.000 fichiers de backup. Mais si c’est le cas, pour lever cette limite, on peut écrire l’itérateur suivant :

1
2
3
4
5
6
7
    public static IEnumerable<int> InfiniteRange(int start)
{
while(true)
{
yield return start++;
}
}


Qui fait en sorte de retourner une nouvelle valeur à chaque fois qu’une est demandée. Pour utiliser cette méthode, il faut bien être sur d’avoir une condition de sortie (le fichier n’existe pas, dans l’exemple précédent), sinon vous pourriez bien énumérer jusqu'à la fin des temps... En fait jusqu'à int.MaxValue, pour les nit-pickers, mais .NET 4.0 propose System.Numerics.BigInteger pour être bien sur d’arriver à la fin de temps. On ne sait jamais.

Enfin, pour utiliser cet itérateur, il faut simplement replacer :

1
        var fileQuery = from index in Enumerable.Range(2, 10000)

par

1
        var fileQuery = from index in InfiniteRange(2)

Et c’est terminé !

Publié jeudi 10 juin 2010 23:05 par jay
Classé sous , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

# re: [LINQ] Trouver le nom de fichier suivant disponible @ samedi 12 juin 2010 14:37

Ah la mode linq, c'est terrible :) Pourtant ce n'est pas adapté à tout! Ici en particulier, ça sert à quoi? C'est moins lisible, moins maintenable, plus long, et moins performant (bien que par rapport aux accès disque, ici on s'en fiche un peu). Trois  autres remarques:

1) le code ne valide pas la chaîne de départ

2) le nombre de fichiers maximum par volume (sous NTFS) est de (2^32 -1) donc pas besoin de BigInteger :)

3) le code ne fonctionne que par rapport au répertoire courant, il faudrait faire plutôt ceci pour être tranquille

       public static string CreateNewFileName(string filePath)

       {

           if (filePath == null)

               throw new ArgumentNullException("filePath");

           if (!File.Exists(filePath))

               return filePath;

           var dir = Path.GetDirectoryName(filePath);

           var name = Path.GetFileNameWithoutExtension(filePath);

           var extension = Path.GetExtension(filePath);

           return (from index in InfiniteRange(2)

                           let fileName = Path.Combine(dir, string.Format("{0} ({1}){2}", name, index, extension))

                           where !File.Exists(fileName)

                           select fileName).First();

       }

et une version Linq-Free:

 public static string CreateNewFileNameSansLinq(string filePath)

       {

           if (filePath == null)

               throw new ArgumentNullException("filePath");

           string dir = Path.GetDirectoryName(filePath);

           string name = Path.GetFileNameWithoutExtension(filePath);

           string extension = Path.GetExtension(filePath);

           string newFilePath = filePath;

           int index = 2;

           do

           {

               if (!File.Exists(newFilePath))

                   return newFilePath;

               newFilePath = Path.Combine(dir, string.Format("{0} ({1}){2}", name, index++, extension));

           }

           while (true);

       }

smo

# re: [LINQ] Trouver le nom de fichier suivant disponible @ samedi 12 juin 2010 15:02

1. En effet, il n'y a pas de validation, mais ce n'est qu'un exemple :) L'analyse statique de vs2010 se charge très bien de le rappeler.

2. Certes. La encore, l'exemple du infinite n'est pas pour avoir plus de 10000 fichiers, mais plus pour ne pas avoir de 10000 écrit dans le code.

3. Il m'avait échappé celui la :)

Enfin, la mode linq, c'est une question de gout, et je préfère de plus en plus la manière sans état qui change dans le code que j'écris.

Il est certain que dans ce contexte, on a le système de fichier qui est finalement un gros état que l'on ne gère pas.

L'exemple était pour Linq, et l'appliquer à autre chose que des objets ou de la database.

Tu ne dois pas vraiment apprécier les Reactive Extensions je pense... non ?

jay


Les 10 derniers blogs postés

- Les actualités de la semaine sur c2i.fr (14 mai - 20 mai) par Richard Clark le il y a 4 heures et 24 minutes

- Reactive Extensions : Consommer des services avec Rx Partie 3, les pièges à éviter par Léonard Labat le il y a 13 heures et 29 minutes

- SharePoint Blog Site, problème d’archives par Le Blog (Vert) d'Arnaud JUND le 05-20-2012, 13:09

- Soirée ALT.NET Mai - 3 présentations par #Rui le 05-18-2012, 11:59

- [ #SharePoint 2010][ #SQLServer 2012] AlwaysOn pour SharePoint (2/4) : Configuration (2e partie)… par Le blog de Patrick [MVP SharePoint] le 05-18-2012, 11:31

- Team Foundation Server 11: tous les trésors cachés du site d’équipe par Philess le 05-16-2012, 19:01

- [PowerShell 3] Télécharger et installer la documentation en ligne par Blog de SPBrouillet (Pierrick BROUILLET) le 05-16-2012, 17:36

- [#SharePoint 2010][#SQLServer 2012] AlwaysOn pour SharePoint (1/4) : Configuration (1ère partie)… par Le blog de Patrick [MVP SharePoint] le 05-16-2012, 12:10

- Job Day @MIC Brussels - .Net Developers on Mobile applications par Le Blog (Vert) d'Arnaud JUND le 05-15-2012, 20:26

- [SharePoint 2010] – SharePoint 2010, Windows (Server) 8 et des erreurs IIS sont dans une VM… par Blog de SPBrouillet (Pierrick BROUILLET) le 05-14-2012, 12:10