views:

126

answers:

4

Does C# have any functions to produce a new name for a copied item ? For example , if I have a string called "Folder" , I need a function to produce a string "Copy of Folder"....and given the string "Copy of Folder" the function should produce "Copy of Folder (1)" and so on ....

+4  A: 

You can write a loop, like this:

string baseName = @"C:\Parent\Copy of Folder", actualName = baseName;
int index = 0;

while(File.Exists(actualName) || Directory.Exists(acutalName))
    actualName = baseName + " (" + (++index) + ");

Depending on your use-case, you should probably put this in a static utility method.

SLaks
+2  A: 

@SLaks has the basic algorithm, but you need to keep in mind race conditions. The file could be created by some other thread or process in the time between the File.Exists check and the creation of the file. Here's an outline of a solution based on SLak's algorithm:

FileStream fileCopy;
while(File.Exists(actualName) || Directory.Exists(acutalName))
{
  actualName = baseName + " (" + (++index) + ")"; 
  try
  {
    fileCopy = new FileStream(actualName, FileMode.CreateNew);
  }
  catch (IOException)
  {
     if (!File.Exists(actualName))
     {
       throw;
     }
  }
}

If you fail to open the file, it is either because some other process created the file or due to some unexpected problem. Re-throw the exception in the case of an unexpected problem (never swallow unexpected exceptions.)

You could just use whether the "new FileStream" operation throws an exception as the loop condition, but I avoid throwing exceptions for non-error conditions. It makes it harder to take advantage of "catch on thrown" exception behavior in the debugger and exceptions are expensive computationally. I'm assuming that it is not an error in the context of your application to have a file on the filesystem with the same name that you would like to use for your copy. If I'm wrong about that, then an exception if the file exists is appropriate in my mind.

David Gladfelter
A: 

I never liked the "Copy of" prefix used by Microsoft, because copies do not sort next to the originals. Microsoft in Windows 7 now agree with me (which is always nice), as it uses a suffix instead of a prefix, something like "Folder - Copy", "Folder - Copy(2)" etc.

ShellShock
A: 

You can also use the built-in Windows function to handle automatic renaming if the target file already exists. That way you will get the same name that you would get when copying the file in Explorer. The function you need is SHFileOperation.

A short sample would look like this:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        string file = @"C:\tmp\test.txt";
        try
        {
            SHFILEOPSTRUCT fileOp = new SHFILEOPSTRUCT();

            fileOp.wFunc = FO_Func.FO_COPY;
            fileOp.fFlags = (ushort)(FILEOP_FLAGS.FOF_RENAMEONCOLLISION);

            // file names need double-null termination
            fileOp.pFrom = file + '\0' + '\0';

            // use the same name as target for demo purpose
            fileOp.pTo = file + '\0' + '\0';

            int hRes = SHFileOperation(ref fileOp);
            if (hRes != 0)
            {
                throw new Win32Exception(hRes);
            }
        }
        catch (Exception ex)
        {
            Trace.WriteLine(ex);
        }
    }

    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    static extern int SHFileOperation([In] ref SHFILEOPSTRUCT lpFileOp);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
    struct SHFILEOPSTRUCT
    {
        public IntPtr hwnd;
        public FO_Func wFunc;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pFrom;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string pTo;
        public ushort fFlags;
        public Int32 fAnyOperationsAborted;
        public IntPtr hNameMappings;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpszProgressTitle;
    }

    public enum FO_Func : uint
    {
        FO_MOVE = 0x0001,
        FO_COPY = 0x0002,
        FO_DELETE = 0x0003,
        FO_RENAME = 0x0004,
    }

    [Flags]
    public enum FILEOP_FLAGS : ushort
    {
        FOF_MULTIDESTFILES = 0x0001,
        FOF_CONFIRMMOUSE = 0x0002,
        FOF_SILENT = 0x0004,  
        FOF_RENAMEONCOLLISION = 0x0008,
        FOF_NOCONFIRMATION = 0x0010,  
        FOF_WANTMAPPINGHANDLE = 0x0020,  
        FOF_ALLOWUNDO = 0x0040,
        FOF_FILESONLY = 0x0080,  
        FOF_SIMPLEPROGRESS = 0x0100,  
        FOF_NOCONFIRMMKDIR = 0x0200,  
        FOF_NOERRORUI = 0x0400,  
        FOF_NOCOPYSECURITYATTRIBS = 0x0800,  
        FOF_NORECURSION = 0x1000,  
        FOF_NO_CONNECTED_ELEMENTS = 0x2000,  
        FOF_WANTNUKEWARNING = 0x4000,  
        FOF_NORECURSEREPARSE = 0x8000, 
    }
}

Using the FOF_WANTMAPPINGHANDLE flag it is also possible to get the generated target file name. For further reading and more elaborate samples see this great article on CodeProject:

C# does Shell, Part 2

0xA3