Во-первых, код в вопросе не дает описанного вывода. Извлекает расширение файла ( "txt"
), а не базовое имя файла ( "hello"
). Для этого нужно вызвать последнюю строку First()
, а не Last()
так ...
static string GetFileBaseNameUsingSplit(string path)
{
string[] pathArr = path.Split('\\');
string[] fileArr = pathArr.Last().Split('.');
string fileBaseName = fileArr.First().ToString();
return fileBaseName;
}
Сделав это изменение, нужно подумать о том, насколько улучшается этот код, это количество мусора, которое он создает:
string[]
, Содержащие один string
для каждого сегмента пути вpath
string[]
Содержащий , по меньшей мере , один string
для каждого .
сегмента пути в прошломpath
Таким образом, извлечение имени базового файла из пути образца "C:\Program Files\hello.txt"
должно производить (временный) object
с "C:"
, "Program Files"
, "hello.txt"
, "hello"
, "txt"
, A string[3]
, и string[2]
. Это может быть важно, если метод вызывается по большому количеству путей. Чтобы улучшить это, мы можем искать path
себя, чтобы найти начальную и конечную точки базового имени и использовать их для создания одного нового string
...
static string GetFileBaseNameUsingSubstringUnsafe(string path)
{
// Fails on paths with no file extension - DO NOT USE!!
int startIndex = path.LastIndexOf('\\') + 1;
int endIndex = path.IndexOf('.', startIndex);
string fileBaseName = path.Substring(startIndex, endIndex - startIndex);
return fileBaseName;
}
Это использует индекс символа после последнего \
в качестве начала базового имени, и оттуда ищет первый, .
чтобы использовать в качестве индекса символа после конца базового имени. Это короче, чем оригинальный код? Не совсем. Это "умнее" решение? Я думаю так. По крайней мере, было бы, если бы не тот факт, что ...
Как видно из комментария, предыдущий метод проблематичен. Хотя это работает, если вы предполагаете, что все пути заканчиваются именем файла с расширением, оно выдаст исключение, если путь заканчивается \
(то есть путь к каталогу) или иным образом не содержит расширения в последнем сегменте. Чтобы это исправить, нам нужно добавить дополнительную проверку для учета, когда endIndex
есть -1
(то .
есть не найден) ...
static string GetFileBaseNameUsingSubstringSafe(string path)
{
int startIndex = path.LastIndexOf('\\') + 1;
int endIndex = path.IndexOf('.', startIndex);
int length = (endIndex >= 0 ? endIndex : path.Length) - startIndex;
string fileBaseName = path.Substring(startIndex, length);
return fileBaseName;
}
Теперь эта версия далеко не короче оригинала, но она более эффективна и (теперь) правильна.
Что касается методов .NET, которые реализуют эту функциональность, многие другие ответы предлагают использовать Path.GetFileNameWithoutExtension()
, что является очевидным, простым решением, но не дает таких же результатов, как код в вопросе. Существует тонкое, но важное различие между GetFileBaseNameUsingSplit()
и Path.GetFileNameWithoutExtension()
( GetFileBaseNameUsingPath()
ниже): первое извлекает все до первого, .
а второе извлекает все до последнего .
. Это не имеет значения для образца path
в вопросе, но взгляните на эту таблицу, сравнивая результаты вышеупомянутых четырех методов при вызове с различными путями ...
| Description | Method | Path | Result |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| Single extension | GetFileBaseNameUsingSplit() | "C:\Program Files\hello.txt" | "hello" |
| Single extension | GetFileBaseNameUsingPath() | "C:\Program Files\hello.txt" | "hello" |
| Single extension | GetFileBaseNameUsingSubstringUnsafe() | "C:\Program Files\hello.txt" | "hello" |
| Single extension | GetFileBaseNameUsingSubstringSafe() | "C:\Program Files\hello.txt" | "hello" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| Double extension | GetFileBaseNameUsingSplit() | "C:\Program Files\hello.txt.ext" | "hello" |
| Double extension | GetFileBaseNameUsingPath() | "C:\Program Files\hello.txt.ext" | "hello.txt" |
| Double extension | GetFileBaseNameUsingSubstringUnsafe() | "C:\Program Files\hello.txt.ext" | "hello" |
| Double extension | GetFileBaseNameUsingSubstringSafe() | "C:\Program Files\hello.txt.ext" | "hello" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| No extension | GetFileBaseNameUsingSplit() | "C:\Program Files\hello" | "hello" |
| No extension | GetFileBaseNameUsingPath() | "C:\Program Files\hello" | "hello" |
| No extension | GetFileBaseNameUsingSubstringUnsafe() | "C:\Program Files\hello" | EXCEPTION: Length cannot be less than zero. (Parameter 'length') |
| No extension | GetFileBaseNameUsingSubstringSafe() | "C:\Program Files\hello" | "hello" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| Leading period | GetFileBaseNameUsingSplit() | "C:\Program Files\.hello.txt" | "" |
| Leading period | GetFileBaseNameUsingPath() | "C:\Program Files\.hello.txt" | ".hello" |
| Leading period | GetFileBaseNameUsingSubstringUnsafe() | "C:\Program Files\.hello.txt" | "" |
| Leading period | GetFileBaseNameUsingSubstringSafe() | "C:\Program Files\.hello.txt" | "" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| Trailing period | GetFileBaseNameUsingSplit() | "C:\Program Files\hello.txt." | "hello" |
| Trailing period | GetFileBaseNameUsingPath() | "C:\Program Files\hello.txt." | "hello.txt" |
| Trailing period | GetFileBaseNameUsingSubstringUnsafe() | "C:\Program Files\hello.txt." | "hello" |
| Trailing period | GetFileBaseNameUsingSubstringSafe() | "C:\Program Files\hello.txt." | "hello" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| Directory path | GetFileBaseNameUsingSplit() | "C:\Program Files\" | "" |
| Directory path | GetFileBaseNameUsingPath() | "C:\Program Files\" | "" |
| Directory path | GetFileBaseNameUsingSubstringUnsafe() | "C:\Program Files\" | EXCEPTION: Length cannot be less than zero. (Parameter 'length') |
| Directory path | GetFileBaseNameUsingSubstringSafe() | "C:\Program Files\" | "" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| Current file path | GetFileBaseNameUsingSplit() | "hello.txt" | "hello" |
| Current file path | GetFileBaseNameUsingPath() | "hello.txt" | "hello" |
| Current file path | GetFileBaseNameUsingSubstringUnsafe() | "hello.txt" | "hello" |
| Current file path | GetFileBaseNameUsingSubstringSafe() | "hello.txt" | "hello" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| Parent file path | GetFileBaseNameUsingSplit() | "..\hello.txt" | "hello" |
| Parent file path | GetFileBaseNameUsingPath() | "..\hello.txt" | "hello" |
| Parent file path | GetFileBaseNameUsingSubstringUnsafe() | "..\hello.txt" | "hello" |
| Parent file path | GetFileBaseNameUsingSubstringSafe() | "..\hello.txt" | "hello" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
| Parent directory path | GetFileBaseNameUsingSplit() | ".." | "" |
| Parent directory path | GetFileBaseNameUsingPath() | ".." | "." |
| Parent directory path | GetFileBaseNameUsingSubstringUnsafe() | ".." | "" |
| Parent directory path | GetFileBaseNameUsingSubstringSafe() | ".." | "" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
... и вы увидите, что это Path.GetFileNameWithoutExtension()
дает разные результаты, когда передается путь, где имя файла имеет двойное расширение или начальный и / или конечный .
. Вы можете попробовать это сами с помощью следующего кода ...
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace SO6921105
{
internal class PathExtractionResult
{
public string Description { get; set; }
public string Method { get; set; }
public string Path { get; set; }
public string Result { get; set; }
}
public static class Program
{
private static string GetFileBaseNameUsingSplit(string path)
{
string[] pathArr = path.Split('\\');
string[] fileArr = pathArr.Last().Split('.');
string fileBaseName = fileArr.First().ToString();
return fileBaseName;
}
private static string GetFileBaseNameUsingPath(string path)
{
return Path.GetFileNameWithoutExtension(path);
}
private static string GetFileBaseNameUsingSubstringUnsafe(string path)
{
// Fails on paths with no file extension - DO NOT USE!!
int startIndex = path.LastIndexOf('\\') + 1;
int endIndex = path.IndexOf('.', startIndex);
string fileBaseName = path.Substring(startIndex, endIndex - startIndex);
return fileBaseName;
}
private static string GetFileBaseNameUsingSubstringSafe(string path)
{
int startIndex = path.LastIndexOf('\\') + 1;
int endIndex = path.IndexOf('.', startIndex);
int length = (endIndex >= 0 ? endIndex : path.Length) - startIndex;
string fileBaseName = path.Substring(startIndex, length);
return fileBaseName;
}
public static void Main()
{
MethodInfo[] testMethods = typeof(Program).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
.Where(method => method.Name.StartsWith("GetFileBaseName"))
.ToArray();
var inputs = new[] {
new { Description = "Single extension", Path = @"C:\Program Files\hello.txt" },
new { Description = "Double extension", Path = @"C:\Program Files\hello.txt.ext" },
new { Description = "No extension", Path = @"C:\Program Files\hello" },
new { Description = "Leading period", Path = @"C:\Program Files\.hello.txt" },
new { Description = "Trailing period", Path = @"C:\Program Files\hello.txt." },
new { Description = "Directory path", Path = @"C:\Program Files\" },
new { Description = "Current file path", Path = "hello.txt" },
new { Description = "Parent file path", Path = @"..\hello.txt" },
new { Description = "Parent directory path", Path = ".." }
};
PathExtractionResult[] results = inputs
.SelectMany(
input => testMethods.Select(
method => {
string result;
try
{
string returnValue = (string) method.Invoke(null, new object[] { input.Path });
result = $"\"{returnValue}\"";
}
catch (Exception ex)
{
if (ex is TargetInvocationException)
ex = ex.InnerException;
result = $"EXCEPTION: {ex.Message}";
}
return new PathExtractionResult() {
Description = input.Description,
Method = $"{method.Name}()",
Path = $"\"{input.Path}\"",
Result = result
};
}
)
).ToArray();
const int ColumnPadding = 2;
ResultWriter writer = new ResultWriter(Console.Out) {
DescriptionColumnWidth = results.Max(output => output.Description.Length) + ColumnPadding,
MethodColumnWidth = results.Max(output => output.Method.Length) + ColumnPadding,
PathColumnWidth = results.Max(output => output.Path.Length) + ColumnPadding,
ResultColumnWidth = results.Max(output => output.Result.Length) + ColumnPadding,
ItemLeftPadding = " ",
ItemRightPadding = " "
};
PathExtractionResult header = new PathExtractionResult() {
Description = nameof(PathExtractionResult.Description),
Method = nameof(PathExtractionResult.Method),
Path = nameof(PathExtractionResult.Path),
Result = nameof(PathExtractionResult.Result)
};
writer.WriteResult(header);
writer.WriteDivider();
foreach (IGrouping<string, PathExtractionResult> resultGroup in results.GroupBy(result => result.Description))
{
foreach (PathExtractionResult result in resultGroup)
writer.WriteResult(result);
writer.WriteDivider();
}
}
}
internal class ResultWriter
{
private const char DividerChar = '-';
private const char SeparatorChar = '|';
private TextWriter Writer { get; }
public ResultWriter(TextWriter writer)
{
Writer = writer ?? throw new ArgumentNullException(nameof(writer));
}
public int DescriptionColumnWidth { get; set; }
public int MethodColumnWidth { get; set; }
public int PathColumnWidth { get; set; }
public int ResultColumnWidth { get; set; }
public string ItemLeftPadding { get; set; }
public string ItemRightPadding { get; set; }
public void WriteResult(PathExtractionResult result)
{
WriteLine(
$"{ItemLeftPadding}{result.Description}{ItemRightPadding}",
$"{ItemLeftPadding}{result.Method}{ItemRightPadding}",
$"{ItemLeftPadding}{result.Path}{ItemRightPadding}",
$"{ItemLeftPadding}{result.Result}{ItemRightPadding}"
);
}
public void WriteDivider()
{
WriteLine(
new string(DividerChar, DescriptionColumnWidth),
new string(DividerChar, MethodColumnWidth),
new string(DividerChar, PathColumnWidth),
new string(DividerChar, ResultColumnWidth)
);
}
private void WriteLine(string description, string method, string path, string result)
{
Writer.Write(SeparatorChar);
Writer.Write(description.PadRight(DescriptionColumnWidth));
Writer.Write(SeparatorChar);
Writer.Write(method.PadRight(MethodColumnWidth));
Writer.Write(SeparatorChar);
Writer.Write(path.PadRight(PathColumnWidth));
Writer.Write(SeparatorChar);
Writer.Write(result.PadRight(ResultColumnWidth));
Writer.WriteLine(SeparatorChar);
}
}
}
TL; DR Код в вопросе не ведет себя так, как многие ожидают в некоторых угловых случаях. Если вы собираетесь написать свой собственный код манипуляции с путями, обязательно примите во внимание ...
- ... как вы определяете «расширение» (это все до первого
.
или все до последнего .
?)
- ... файлы с несколькими расширениями
- ... файлы без расширения
- ... файлы с лидирующей
.
- ... файлы с трейлингом
.
(вероятно, это не то, с чем вы когда-либо сталкивались в Windows, но они возможны )
- ... каталоги с "расширением" или которые в противном случае содержат
.
- ... пути, которые заканчиваются
\
- ... относительные пути
Не все пути к файлам следуют обычной формуле X:\Directory\File.ext
!
Path.GetFileName("C:\\dev\\some\\path\\to\\file.cs")
возвращает ту же строку и по какой-то причине не преобразует ее в «file.cs». Если я копирую / вставляю свой код в онлайн-компилятор (например, rextester.com ), он работает ...?