Po pierwsze, kod w pytaniu nie generuje opisanego wyniku. Wyodrębnia rozszerzenie pliku ( "txt"
), a nie podstawową nazwę pliku ( "hello"
). Aby to zrobić, ostatnia linia powinna zadzwonić First()
, a nie Last()
tak ...
static string GetFileBaseNameUsingSplit(string path)
{
string[] pathArr = path.Split('\\');
string[] fileArr = pathArr.Last().Split('.');
string fileBaseName = fileArr.First().ToString();
return fileBaseName;
}
Po dokonaniu tej zmiany, jedną z rzeczy, o których należy pomyśleć w zakresie poprawy tego kodu, jest ilość śmieci, które tworzy:
string[]
Zawierający jeden string
dla każdego odcinka toru wpath
string[]
Zawierający co najmniej jedną string
dla każdego .
segmentu ostatnią drogępath
Dlatego też, ekstrahowanie nazwy pliku, z drogi próbkowania "C:\Program Files\hello.txt"
powinna produkować (tymczasowego) object
y "C:"
, "Program Files"
, "hello.txt"
, "hello"
, "txt"
, A string[3]
i string[2]
. Może to mieć znaczenie, jeśli metoda zostanie wywołana na dużej liczbie ścieżek. Aby to poprawić, możemy przeszukać path
siebie, aby zlokalizować punkty początkowe i końcowe nazwy bazowej i użyć ich do utworzenia jednego nowego 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;
}
Wykorzystuje to indeks znaku po ostatnim \
jako początek nazwy podstawowej, a stamtąd szuka pierwszego, .
który będzie używany jako indeks znaku po końcu nazwy podstawowej. Czy to jest krótszy niż oryginalny kod? Nie do końca. Czy to „mądrzejsze” rozwiązanie? Chyba tak. Przynajmniej byłoby tak, gdyby nie fakt, że ...
Jak widać z komentarza, poprzednia metoda jest problematyczna. Chociaż działa, jeśli założysz, że wszystkie ścieżki kończą się nazwą pliku z rozszerzeniem, zgłasza wyjątek, jeśli ścieżka kończy się na \
(tj. Ścieżka katalogu) lub w inny sposób nie zawiera rozszerzenia w ostatnim segmencie. Aby to naprawić, musimy dodać dodatkowy czek, aby uwzględnić, kiedy endIndex
jest -1
(tzn. .
Nie został znaleziony) ...
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;
}
Teraz ta wersja nie jest prawie krótsza niż oryginalna, ale jest również bardziej wydajna i (teraz) poprawna.
Jeśli chodzi o metody .NET, które implementują tę funkcjonalność, wiele innych odpowiedzi sugeruje użycie Path.GetFileNameWithoutExtension()
, co jest oczywistym, łatwym rozwiązaniem, ale nie daje takich samych wyników jak kod w pytaniu. Istnieje subtelna, ale ważna różnica między GetFileBaseNameUsingSplit()
i Path.GetFileNameWithoutExtension()
( GetFileBaseNameUsingPath()
poniżej): pierwszy wyodrębnia wszystko przed pierwszym, .
a drugi wyodrębnia wszystko przed ostatnim .
. Nie ma to znaczenia dla próbki path
w pytaniu, ale spójrz na tabelę porównującą wyniki powyższych czterech metod, gdy są wywoływane z różnymi ścieżkami ...
| 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() | ".." | "" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
... a zobaczysz, że Path.GetFileNameWithoutExtension()
daje to inne wyniki, gdy przejdziesz ścieżkę, w której nazwa pliku ma podwójne rozszerzenie lub początkowe i / lub końcowe .
. Możesz spróbować samemu, używając następującego kodu ...
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 Kod w pytaniu nie zachowuje się tak, jak wielu wydaje się oczekiwać w niektórych przypadkach narożnych. Jeśli masz zamiar napisać własny kod do manipulacji ścieżką, pamiętaj, aby wziąć pod uwagę ...
- ... jak zdefiniujesz „rozszerzenie” (czy to wszystko przed pierwszym,
.
czy wszystko przed ostatnim .
?)
- ... pliki z wieloma rozszerzeniami
- ... pliki bez rozszerzenia
- ... pliki z wiodącym
.
- ... pliki ze znakiem końcowym
.
(prawdopodobnie nie jest to coś, co można napotkać w systemie Windows, ale są one możliwe )
- ... katalogi z „rozszerzeniem” lub zawierające w inny sposób
.
- ... ścieżki kończące się na
\
- ... ścieżki względne
Nie wszystkie ścieżki plików są zgodne ze zwykłą formułą X:\Directory\File.ext
!
Path.GetFileName("C:\\dev\\some\\path\\to\\file.cs")
zwraca ten sam ciąg i z jakiegoś powodu nie konwertuje go do pliku „file.cs”. Jeśli skopiuję / wkleję mój kod do kompilatora online (takiego jak rextester.com ), to zadziała ...?