Thứ nhất, mã trong câu hỏi không tạo ra đầu ra được mô tả. Nó trích xuất phần mở rộng tệp ( "txt"
) chứ không phải tên cơ sở tệp ( "hello"
). Để làm điều đó, dòng cuối cùng nên gọi First()
, không Last()
, như thế này ...
static string GetFileBaseNameUsingSplit(string path)
{
string[] pathArr = path.Split('\\');
string[] fileArr = pathArr.Last().Split('.');
string fileBaseName = fileArr.First().ToString();
return fileBaseName;
}
Đã thực hiện thay đổi đó, một điều cần suy nghĩ về việc cải thiện mã này là lượng rác mà nó tạo ra:
- Một
string[]
chứa một string
cho mỗi đoạn đường dẫn trongpath
- Một
string[]
chứa ít nhất một string
cho mỗi .
trong đoạn đường dẫn cuối cùng trongpath
Do đó, giải nén tên tập tin cơ sở từ đường dẫn mẫu "C:\Program Files\hello.txt"
nên sản xuất (tạm thời) object
s "C:"
, "Program Files"
, "hello.txt"
, "hello"
, "txt"
, một string[3]
, và một string[2]
. Điều này có thể có ý nghĩa nếu phương thức được gọi trên một số lượng lớn đường dẫn. Để cải thiện điều này, chúng ta có thể tìm kiếm path
chính mình để xác định vị trí bắt đầu và điểm kết thúc của tên cơ sở và sử dụng những để tạo ra một mới 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;
}
Điều này đang sử dụng chỉ mục của ký tự sau cùng \
là tên bắt đầu của tên cơ sở và từ đó tìm kiếm đầu tiên .
để sử dụng làm chỉ mục của ký tự sau khi kết thúc tên cơ sở. Đây có phải là ngắn hơn so với mã ban đầu? Không hẳn. Đó có phải là một giải pháp "thông minh hơn"? Tôi nghĩ vậy. Ít nhất, nó sẽ là nếu không phải vì ...
Như bạn có thể thấy từ các bình luận, phương pháp trước đó là có vấn đề. Mặc dù nó hoạt động nếu bạn giả sử tất cả các đường dẫn kết thúc bằng một tên tệp có phần mở rộng, nó sẽ đưa ra một ngoại lệ nếu đường dẫn kết thúc bằng \
(tức là đường dẫn thư mục) hoặc nếu không chứa phần mở rộng trong phân đoạn cuối. Để khắc phục điều này, chúng ta cần thêm một kiểm tra thêm vào tài khoản khi endIndex
là -1
(tức .
là không tìm thấy) ...
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;
}
Bây giờ phiên bản này gần như ngắn hơn bản gốc, nhưng nó hiệu quả hơn và (bây giờ) cũng đúng.
Theo như các phương thức .NET thực hiện chức năng này, nhiều câu trả lời khác đề nghị sử dụng Path.GetFileNameWithoutExtension()
, đây là một giải pháp rõ ràng, dễ dàng nhưng không tạo ra kết quả giống như mã trong câu hỏi. Có một sự khác biệt tinh tế nhưng quan trọng giữa GetFileBaseNameUsingSplit()
và Path.GetFileNameWithoutExtension()
( GetFileBaseNameUsingPath()
bên dưới): cái trước trích xuất mọi thứ trước cái đầu tiên .
và cái sau trích ra mọi thứ trước cái cuối cùng .
. Điều này không tạo ra sự khác biệt cho mẫu path
trong câu hỏi, nhưng hãy xem bảng này so sánh kết quả của bốn phương pháp trên khi được gọi với các đường dẫn khác nhau ...
| 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() | ".." | "" |
|-----------------------|---------------------------------------|----------------------------------|------------------------------------------------------------------|
... và bạn sẽ thấy rằng Path.GetFileNameWithoutExtension()
mang lại kết quả khác nhau khi đi qua một đường dẫn trong đó tên tệp có phần mở rộng gấp đôi hoặc dẫn đầu và / hoặc theo dõi .
. Bạn có thể tự thử nó với mã sau đây ...
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 Mã trong câu hỏi không hoạt động như nhiều người mong đợi trong một số trường hợp góc. Nếu bạn định viết mã thao tác đường dẫn của riêng mình, hãy chắc chắn xem xét ...
- ... Làm thế nào bạn xác định một "phần mở rộng" (nó là tất cả mọi thứ trước cái đầu tiên
.
hay mọi thứ trước cái cuối cùng .
?)
- ... tập tin có nhiều phần mở rộng
- ... tập tin không có phần mở rộng
- ... tập tin hàng đầu
.
- ... Các tệp có dấu
.
(có thể không phải là thứ bạn sẽ gặp trên Windows, nhưng chúng có thể )
- ... thư mục có "phần mở rộng" hoặc nếu không có
.
- ... những con đường kết thúc bằng một
\
- ... đường dẫn tương đối
Không phải tất cả các đường dẫn tập tin theo công thức thông thường của X:\Directory\File.ext
!
Path.GetFileName("C:\\dev\\some\\path\\to\\file.cs")
đang trả lại cùng một chuỗi và không chuyển đổi nó thành "file.cs" vì một số lý do. Nếu tôi sao chép / dán mã của mình vào trình biên dịch trực tuyến (như rextester.com ), nó có hoạt động không ...?