文件移动失败 - 文件已存在

99

我有一个文件夹:

c:\test

我正在尝试运行这段代码:

File.Move(@"c:\test\SomeFile.txt", @"c:\test\Test");

我遇到了异常:

文件已存在

输出目录肯定存在,输入文件也在那里。


2
如果输入文件已经在输出目录中,则该文件已存在,因此解释了异常。您需要指示您希望原始文件被新文件覆盖 - Cody Gray
11
听起来错误提示已经准确地告诉了你出了什么问题。 - Josh
@binki POSIX是无关紧要的(您是否指的是原子操作?),NTFS确实支持真正的事务操作,例如回滚并获取原始文件内容。正如其他人所回答的,Win32允许移动并替换。这是.NET的File.Move没有提供功能。您可以使用AlphaFS等库获得Move with replace和事务操作。 - Panagiotis Kanavos
@PanagiotisKanavos .net的API是Win32 API的反映,但排除了像FileMoveEx()这样有用的东西。因此,.net缺少类似于POSIX的FS层。.net确实公开了File.Replace(),但试图使用它来实现类似于POSIX的rename()会导致竞争条件。如果框架公开了类似于POSIX的rename(),将使人们的生活更轻松,并允许人们编写更纯净(少P/Invoke)的代码。 - binki
3
无论论坛上的讨论如何,@binki指出,在不同的文件系统中行为都是定义明确的。File.Move未调用Ex或Transacted方法的原因是因为FAT文件系统不具有原子性且不会表现相同。重命名不是元数据操作,需要实际移动数据。至于事务和写时复制,请忘记它们。在我看来这不是一个好的决定。 - Panagiotis Kanavos
显示剩余3条评论
9个回答

164

你需要的是:

if (!File.Exists(@"c:\test\Test\SomeFile.txt")) {
    File.Move(@"c:\test\SomeFile.txt", @"c:\test\Test\SomeFile.txt");
}
或者
if (File.Exists(@"c:\test\Test\SomeFile.txt")) {
    File.Delete(@"c:\test\Test\SomeFile.txt");
}
File.Move(@"c:\test\SomeFile.txt", @"c:\test\Test\SomeFile.txt");

这将会:

  • 如果文件不存在于目标位置,成功移动文件;或者
  • 如果文件已经存在于目标位置,删除该文件,然后移动文件。

编辑:我应该澄清一下我的回答,尽管它是最受欢迎的!File.Move的第二个参数应该是 目标文件 - 而不是文件夹。您将第二个参数指定为目标文件夹,而不是目标文件名 - 这就是File.Move所需要的。

因此,您的第二个参数应该是c:\test\Test\SomeFile.txt


4
如果您的应用程序是多线程的(或者有其他进程正在处理您的文件),即使使用“if(Exists)Delete”代码,仍然可能会出现相同的异常。因为仍然存在一个时间空间,在删除后另一个线程/进程可能会将文件放回去,然后您进行移动,最终仍然会出现异常。记住这一点是值得的 :-) - bytedev
14
对于大多数在尝试覆盖现有文件后在谷歌上搜索的人来说,本答案仍然有效。在这种困境中,大多数人并不像原帖中那样存在语法/拼写错误问题。 - WEFX
2
有趣的是,如果文件不存在,File.Delete确实可以正常工作并且什么也不做。但是,如果路径中的任何一个目录不存在,则会引发DirectoryNotFoundException异常。 - Brandon Barkley
2
@JirkaHanika 你可以把 if(File.Exists) 改成 while(File.Exists)。 - Brandon Barkley
1
从 .Net core 3.0 开始,有一种更简单的方法。如果你被困在旧版本中,我相信这个答案会更少出错。这个答案 - Arkane
显示剩余8条评论

67

您需要将它移动到另一个文件中(而不是文件夹),这也可以用于重命名。

移动:

File.Move(@"c:\test\SomeFile.txt", @"c:\test\Test\SomeFile.txt");

重命名:

File.Move(@"c:\test\SomeFile.txt", @"c:\test\SomeFile2.txt");

在您的示例中显示“文件已存在”的原因是,C:\test\Test 尝试创建一个没有扩展名的文件 Test,但由于已经存在同名的文件夹,所以无法创建。


1
这个答案帮助我理解了为什么Linux会使用.d作为目录后缀,即使同名的文件已经存在。谢谢! - Jamie Howarth

53

就个人而言,我更喜欢这种方法。 这将覆盖目标文件中的内容,删除源文件,并且在复制失败时也会防止删除源文件。

string source = @"c:\test\SomeFile.txt";
string destination = @"c:\test\test\SomeFile.txt";

try
{
    File.Copy(source, destination, true);
    File.Delete(source);
}
catch
{
    //some error handling
}

6
对于小文件(且不需要原子移动要求)来说,这样做还好,但对于大文件或者需要确保不会出现重复的情况来说,这就有问题了。 - River Satya
10
File.Move没有覆盖选项。 - Mitchell
4
根据您的使用情况,这可能会导致问题。在文件系统监视器中,“移动”是一个真实的事件。监听文件系统事件的某些内容将获得删除和创建事件,而不是移动事件。这也将更改底层文件系统ID。 - Andrew Rondeau
2
这对于大文件来说性能不会很低吗?如果源和目标在同一个物理卷上,你将无缘无故地创建第二个副本,然后删除原始文件,而File.Move()则会避免在源和目标在同一卷上时做额外的工作。 - Brad Westness
5
在.NET v3.0中,他们添加了“overwrite”(覆盖)参数。仅作记录。 - Soren
显示剩余4条评论

22
你可以使用P/Invoke调用 MoveFileEx() - 将flags参数设置为11MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)。
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
static extern bool MoveFileEx(string existingFileName, string newFileName, int flags);

或者,你可以直接打电话

Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(existingFileName, newFileName, true);

在添加了Microsoft.VisualBasic作为参考之后。


如果应用程序仅在Windows上运行,则完全没问题。对于大多数愿意尝试一些P/Invoke的人来说,这可能是一个不错的答案。 - Kind Contributor
我曾经想重新开发这个方法,但是由于VisualBasic的解决方案已经很可靠和适当了,所以我放弃了这个想法。 - Tib Schott

14
  1. 在 .Net Core 3.0 及以上版本的 C# 中,现在有了第三个布尔参数:

在 .NET Core 3.0 及以后的版本中,您可以调用 Move(String, String, Boolean) 方法,并将参数 overwrite 设置为 true,这将替换文件(如果存在)。

来源:Microsoft Docs

  1. 对于所有其他版本的 .Net,此答案是最佳方法。先复制并覆盖源文件,然后再删除源文件。这样做更好,因为它会使其成为一个原子操作。(我已尝试使用此更新 MS Docs)

11

如果文件确实存在,且您想要替换它,请使用下面的代码:

string file = "c:\test\SomeFile.txt"
string moveTo = "c:\test\test\SomeFile.txt"

if (File.Exists(moveTo))
{
    File.Delete(moveTo);
}

File.Move(file, moveTo);

4
根据File.Move文档,没有“如果存在则覆盖”的参数。您尝试指定目标文件夹,但必须给出完整的文件规范。重新阅读文档(“提供指定新文件名的选项”),我认为在目标文件夹规范末尾添加反斜杠可能有效。

文档提到:请注意,如果您尝试通过将同名文件移动到该目录来替换文件,则会抛出IOException异常。为此,请调用Move(String,String,Boolean)。但这似乎是一个错误? - Kevin Scharnhorst
2
@KevinScharnhorst 这个回答是在2011年的。现在的文档已经包括了对于Move with Overwrite的 .Net Core 3.0支持。 - Kind Contributor

2
尝试使用 Microsoft.VisualBasic.FileIO.FileSystem.MoveFile(Source, Destination, True)。最后一个参数是覆盖开关,而 System.IO.File.Move 没有此功能。

2
这里已经有一个类似的答案,建议您查看 https://dev59.com/SW025IYBdhLWcg3wl3Am#42224803 - JG in SD
1
这是建议相同的答案:https://dev59.com/SW025IYBdhLWcg3wl3Am#38372760,而不是stackoverflow.com/a/42224803/1236734。 - Kind Contributor

2
如果您没有在新位置删除已经存在的文件的选项,但仍然需要从原始位置移动和删除文件,那么这个重命名的技巧可能会有用:
string newFileLocation = @"c:\test\Test\SomeFile.txt";

while (File.Exists(newFileLocation)) {
    newFileLocation = newFileLocation.Split('.')[0] + "_copy." + newFileLocation.Split('.')[1];
}
File.Move(@"c:\test\SomeFile.txt", newFileLocation);

这假设文件名中唯一的 '.' 是在扩展名之前。 它将文件分成两部分,扩展名之前和之后,在它们之间附加 "_copy."。 这样可以移动文件,但如果文件已经存在或者已经存在副本或者已经存在副本的副本,或者已经存在副本的副本的副本... 就会创建一个副本。 ;)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接