訓練
模組
瞭解如何使用 System.IO 類別來管理本機檔案和目錄,以及如何使用 CSV 檔案和 StreamReader 和 StreamWriter 類別來儲存和擷取 C# 物件。
許多文件系統作業基本上都是查詢,因此非常適合 LINQ 方法。 這些查詢不具破壞性。 它們不會變更源檔或資料夾的內容。 查詢不應該造成任何副作用。 一般而言,修改源數據的任何程序代碼(包括執行建立/更新/刪除作業的查詢),都應該與只查詢數據的程式代碼分開。
建立數據源有一些複雜度,可準確地表示文件系統的內容,並正常處理例外狀況。 本節中的範例會建立 物件的快照集集合,此集合 FileInfo 代表指定根資料夾及其所有子資料夾下的所有檔案。 您開始到結束執行查詢之間,每個 FileInfo 的狀態可能會隨時間變化。 例如,您可以建立要作為數據源的物件 FileInfo 清單。 如果您嘗試存取查詢中的 屬性,物件FileInfo會嘗試存取Length
檔案系統以更新 的值Length
。 如果檔案已不存在,即便您未直接查詢檔案系統,在查詢中仍會得到FileNotFoundException。
這個範例示範如何在指定的目錄樹狀目錄中尋找具有指定擴展名的所有檔案(例如“.txt”。 它也會示範如何根據建立時間傳回樹狀結構中最新的或最舊檔案。 無論您是在 Windows、Mac 或 Linux 系統上執行此程式碼,您可能需要修改許多範例的第一行。
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
DirectoryInfo dir = new DirectoryInfo(startFolder);
var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
var fileQuery = from file in fileList
where file.Extension == ".txt"
orderby file.Name
select file;
// Uncomment this block to see the full query
// foreach (FileInfo fi in fileQuery)
// {
// Console.WriteLine(fi.FullName);
// }
var newestFile = (from file in fileQuery
orderby file.CreationTime
select new { file.FullName, file.CreationTime })
.Last();
Console.WriteLine($"\r\nThe newest .txt file is {newestFile.FullName}. Creation time: {newestFile.CreationTime}");
此範例示範如何使用 LINQ,在檔案或資料夾清單上執行進階群組和排序作業。 它也會示範如何使用 Skip 和 Take 方法,在控制台視窗中分頁輸出。
下列查詢示範如何依擴展名將指定目錄樹狀目錄的內容分組。
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
int trimLength = startFolder.Length;
DirectoryInfo dir = new DirectoryInfo(startFolder);
var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
var queryGroupByExt = from file in fileList
group file by file.Extension.ToLower() into fileGroup
orderby fileGroup.Count(), fileGroup.Key
select fileGroup;
// Iterate through the outer collection of groups.
foreach (var filegroup in queryGroupByExt.Take(5))
{
Console.WriteLine($"Extension: {filegroup.Key}");
var resultPage = filegroup.Take(20);
//Execute the resultPage query
foreach (var f in resultPage)
{
Console.WriteLine($"\t{f.FullName.Substring(trimLength)}");
}
Console.WriteLine();
}
此程序的輸出可能很長,這取決於本機文件系統的詳細資訊,以及 startFolder
的設定值。 若要啟用檢視所有結果,此範例示範如何逐頁檢視結果。 巢狀 foreach
迴圈是必要的,因為每個群組會個別列舉。
這個範例示範如何擷取指定資料夾中所有檔案及其所有子資料夾中所使用的位元組總數。 方法 Sum 會加入 子句中 select
選取的所有專案值。 您可以呼叫 Min 或 Max 方法來取代 Sum,以修改此查詢,以擷取指定目錄樹狀目錄中的最大或最小檔案。
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
var fileList = Directory.GetFiles(startFolder, "*.*", SearchOption.AllDirectories);
var fileQuery = from file in fileList
let fileLen = new FileInfo(file).Length
where fileLen > 0
select fileLen;
// Cache the results to avoid multiple trips to the file system.
long[] fileLengths = fileQuery.ToArray();
// Return the size of the largest file
long largestFile = fileLengths.Max();
// Return the total number of bytes in all the files under the specified folder.
long totalBytes = fileLengths.Sum();
Console.WriteLine($"There are {totalBytes} bytes in {fileList.Count()} files under {startFolder}");
Console.WriteLine($"The largest file is {largestFile} bytes.");
此範例會擴充上一個範例來執行下列動作:
下列範例包含五個不同的查詢,顯示如何根據檔案大小以位元組為單位來查詢和群組檔案。 您可以修改這些範例,以將查詢以物件的某些其他屬性 FileInfo 為基礎。
// Return the FileInfo object for the largest file
// by sorting and selecting from beginning of list
FileInfo longestFile = (from file in fileList
let fileInfo = new FileInfo(file)
where fileInfo.Length > 0
orderby fileInfo.Length descending
select fileInfo
).First();
Console.WriteLine($"The largest file under {startFolder} is {longestFile.FullName} with a length of {longestFile.Length} bytes");
//Return the FileInfo of the smallest file
FileInfo smallestFile = (from file in fileList
let fileInfo = new FileInfo(file)
where fileInfo.Length > 0
orderby fileInfo.Length ascending
select fileInfo
).First();
Console.WriteLine($"The smallest file under {startFolder} is {smallestFile.FullName} with a length of {smallestFile.Length} bytes");
//Return the FileInfos for the 10 largest files
var queryTenLargest = (from file in fileList
let fileInfo = new FileInfo(file)
let len = fileInfo.Length
orderby len descending
select fileInfo
).Take(10);
Console.WriteLine($"The 10 largest files under {startFolder} are:");
foreach (var v in queryTenLargest)
{
Console.WriteLine($"{v.FullName}: {v.Length} bytes");
}
// Group the files according to their size, leaving out
// files that are less than 200000 bytes.
var querySizeGroups = from file in fileList
let fileInfo = new FileInfo(file)
let len = fileInfo.Length
where len > 0
group fileInfo by (len / 100000) into fileGroup
where fileGroup.Key >= 2
orderby fileGroup.Key descending
select fileGroup;
foreach (var filegroup in querySizeGroups)
{
Console.WriteLine($"{filegroup.Key}00000");
foreach (var item in filegroup)
{
Console.WriteLine($"\t{item.Name}: {item.Length}");
}
}
若要傳回一或多個完整的 FileInfo 對象,查詢必須先檢查數據源中的每個物件,然後依其 Length 屬性的值加以排序。 然後,它可以傳回長度最大的單一或序列。 使用 First 傳回清單中的第一個元素。 使用 Take 傳回前 n 個元素數目。 指定遞減排序順序,將最小的元素放在清單開頭。
有時候具有相同名稱的檔案可以位於多個資料夾中。 這個範例示範如何在指定的根資料夾下查詢這類重複的檔名。 第二個範例示範如何查詢大小和 LastWrite 時間也相符的檔案。
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
DirectoryInfo dir = new DirectoryInfo(startFolder);
IEnumerable<FileInfo> fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
// used in WriteLine to keep the lines shorter
int charsToSkip = startFolder.Length;
// var can be used for convenience with groups.
var queryDupNames = from file in fileList
group file.FullName.Substring(charsToSkip) by file.Name into fileGroup
where fileGroup.Count() > 1
select fileGroup;
foreach (var queryDup in queryDupNames.Take(20))
{
Console.WriteLine($"Filename = {(queryDup.Key.ToString() == string.Empty ? "[none]" : queryDup.Key.ToString())}");
foreach (var fileName in queryDup.Take(10))
{
Console.WriteLine($"\t{fileName}");
}
}
第一個查詢會使用索引鍵來判定匹配。 它會尋找具有相同名稱但內容可能不同的檔案。 第二個查詢會使用複合索引鍵來比對FileInfo 對象的三個屬性。 此查詢更有可能尋找具有相同名稱和類似或相同內容的檔案。
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
// Make the lines shorter for the console display
int charsToSkip = startFolder.Length;
// Take a snapshot of the file system.
DirectoryInfo dir = new DirectoryInfo(startFolder);
IEnumerable<FileInfo> fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
// Note the use of a compound key. Files that match
// all three properties belong to the same group.
// A named type is used to enable the query to be
// passed to another method. Anonymous types can also be used
// for composite keys but cannot be passed across method boundaries
//
var queryDupFiles = from file in fileList
group file.FullName.Substring(charsToSkip) by
(Name: file.Name, LastWriteTime: file.LastWriteTime, Length: file.Length )
into fileGroup
where fileGroup.Count() > 1
select fileGroup;
foreach (var queryDup in queryDupFiles.Take(20))
{
Console.WriteLine($"Filename = {(queryDup.Key.ToString() == string.Empty ? "[none]" : queryDup.Key.ToString())}");
foreach (var fileName in queryDup)
{
Console.WriteLine($"\t{fileName}");
}
}
}
此範例示範如何查詢指定目錄樹狀目錄中的所有檔案、開啟每個檔案,以及檢查其內容。 這種類型的技術可用來建立目錄樹狀目錄內容的索引或反向索引。 在此範例中會執行簡單的字串搜尋。 不過,可以使用正則表達式來執行更複雜的模式比對類型。
string startFolder = """C:\Program Files\dotnet\sdk""";
// Or
// string startFolder = "/usr/local/share/dotnet/sdk";
DirectoryInfo dir = new DirectoryInfo(startFolder);
var fileList = dir.GetFiles("*.*", SearchOption.AllDirectories);
string searchTerm = "change";
var queryMatchingFiles = from file in fileList
where file.Extension == ".txt"
let fileText = File.ReadAllText(file.FullName)
where fileText.Contains(searchTerm)
select file.FullName;
// Execute the query.
Console.WriteLine($"""The term "{searchTerm}" was found in:""");
foreach (string filename in queryMatchingFiles)
{
Console.WriteLine(filename);
}
此範例示範三種方式來比較兩個檔案清單:
您可以調整此處所示的技術,以比較任何類型的物件序列。
FileComparer
此處顯示的類別示範如何使用自定義比較子類別與標準查詢運算符。 該類別並非設計用於真實世界的情境。 它只會使用每個檔案的名稱和長度,以位元組為單位來判斷每個資料夾的內容是否相同。 在真實世界的案例中,您應該修改此比較器來進行更嚴格的等值檢查。
// This implementation defines a very simple comparison
// between two FileInfo objects. It only compares the name
// of the files being compared and their length in bytes.
class FileCompare : IEqualityComparer<FileInfo>
{
public bool Equals(FileInfo? f1, FileInfo? f2)
{
return (f1?.Name == f2?.Name &&
f1?.Length == f2?.Length);
}
// Return a hash that reflects the comparison criteria. According to the
// rules for IEqualityComparer<T>, if Equals is true, then the hash codes must
// also be equal. Because equality as defined here is a simple value equality, not
// reference identity, it is possible that two or more objects will produce the same
// hash code.
public int GetHashCode(FileInfo fi)
{
string s = $"{fi.Name}{fi.Length}";
return s.GetHashCode();
}
}
public static void CompareDirectories()
{
string pathA = """C:\Program Files\dotnet\sdk\8.0.104""";
string pathB = """C:\Program Files\dotnet\sdk\8.0.204""";
DirectoryInfo dir1 = new DirectoryInfo(pathA);
DirectoryInfo dir2 = new DirectoryInfo(pathB);
IEnumerable<FileInfo> list1 = dir1.GetFiles("*.*", SearchOption.AllDirectories);
IEnumerable<FileInfo> list2 = dir2.GetFiles("*.*", SearchOption.AllDirectories);
//A custom file comparer defined below
FileCompare myFileCompare = new FileCompare();
// This query determines whether the two folders contain
// identical file lists, based on the custom file comparer
// that is defined in the FileCompare class.
// The query executes immediately because it returns a bool.
bool areIdentical = list1.SequenceEqual(list2, myFileCompare);
if (areIdentical == true)
{
Console.WriteLine("the two folders are the same");
}
else
{
Console.WriteLine("The two folders are not the same");
}
// Find the common files. It produces a sequence and doesn't
// execute until the foreach statement.
var queryCommonFiles = list1.Intersect(list2, myFileCompare);
if (queryCommonFiles.Any())
{
Console.WriteLine($"The following files are in both folders (total number = {queryCommonFiles.Count()}):");
foreach (var v in queryCommonFiles.Take(10))
{
Console.WriteLine(v.Name); //shows which items end up in result list
}
}
else
{
Console.WriteLine("There are no common files in the two folders.");
}
// Find the set difference between the two folders.
var queryList1Only = (from file in list1
select file)
.Except(list2, myFileCompare);
Console.WriteLine();
Console.WriteLine($"The following files are in list1 but not list2 (total number = {queryList1Only.Count()}):");
foreach (var v in queryList1Only.Take(10))
{
Console.WriteLine(v.FullName);
}
var queryList2Only = (from file in list2
select file)
.Except(list1, myFileCompare);
Console.WriteLine();
Console.WriteLine($"The following files are in list2 but not list1 (total number = {queryList2Only.Count()}:");
foreach (var v in queryList2Only.Take(10))
{
Console.WriteLine(v.FullName);
}
}
逗號分隔值 (CSV) 檔案是文字檔,通常用來儲存電子表格數據或其他以數據列和數據行表示的表格式數據。 藉由使用 Split 方法來分隔欄位,即可輕鬆地使用 LINQ 查詢以及操作 CSV 檔案。 事實上,相同的技術可以用來重新排列任何結構化文字行的部分:不限於 CSV 檔案。
在下列範例中,假設這三個數據行代表學生的「姓氏」、「名字」和「標識碼」。這些欄位會根據學生的姓氏依字母順序排列。 查詢會產生新的序列,其中標識符數據行會先出現,後面接著第二個數據行,結合學生的名字和姓氏。 這些行會根據 [ID] 欄位重新排序。 結果會儲存到新的檔案中,而且不會修改原始數據。 下列文字顯示下列範例中使用的 spreadsheet1.csv 檔案內容:
Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121
下列程式代碼會讀取原始程序檔,並重新排列 CSV 檔案中的每個數據行,以重新排列資料行的順序:
string[] lines = File.ReadAllLines("spreadsheet1.csv");
// Create the query. Put field 2 first, then
// reverse and combine fields 0 and 1 from the old field
IEnumerable<string> query = from line in lines
let fields = line.Split(',')
orderby fields[2]
select $"{fields[2]}, {fields[1]} {fields[0]}";
File.WriteAllLines("spreadsheet2.csv", query.ToArray());
/* Output to spreadsheet2.csv:
111, Svetlana Omelchenko
112, Claire O'Donnell
113, Sven Mortensen
114, Cesar Garcia
115, Debra Garcia
116, Fadi Fakhouri
117, Hanying Feng
118, Hugo Garcia
119, Lance Tucker
120, Terry Adams
121, Eugene Zabokritski
122, Michael Tucker
*/
此範例示範合併兩個檔案內容的其中一種方式,然後建立一組以新方式組織數據的新檔案。 查詢會使用兩個檔案的內容。 下列文字顯示第一個檔案的內容, names1.txt:
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
第二個檔案 names2.txt包含一組不同的名稱,其中一些名稱與第一個集合通用:
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
下列程式代碼會查詢這兩個檔案,並取得這兩個檔案的聯集,然後為每個群組寫入新的檔案,由系列名稱的第一個字母所定義:
string[] fileA = File.ReadAllLines("names1.txt");
string[] fileB = File.ReadAllLines("names2.txt");
// Concatenate and remove duplicate names
var mergeQuery = fileA.Union(fileB);
// Group the names by the first letter in the last name.
var groupQuery = from name in mergeQuery
let n = name.Split(',')[0]
group name by n[0] into g
orderby g.Key
select g;
foreach (var g in groupQuery)
{
string fileName = $"testFile_{g.Key}.txt";
Console.WriteLine(g.Key);
using StreamWriter sw = new StreamWriter(fileName);
foreach (var item in g)
{
sw.WriteLine(item);
// Output to console for example purposes.
Console.WriteLine($" {item}");
}
}
/* Output:
A
Aw, Kam Foo
B
Bankov, Peter
Beebe, Ann
E
El Yassir, Mehdi
G
Garcia, Hugo
Guy, Wey Yuan
Garcia, Debra
Gilchrist, Beth
Giakoumakis, Leo
H
Holm, Michael
L
Liu, Jinghao
M
Myrcha, Jacek
McLin, Nkenge
N
Noriega, Fabricio
P
Potra, Cristina
T
Toyoshima, Tim
*/
此範例示範如何聯結兩個逗號分隔檔案中的數據,這些檔案共享作為比對索引鍵的通用值。 如果您必須將來自兩個電子表格的數據,或從具有另一種格式的檔案,或從具有另一種格式的檔案將數據合併成新檔案,則這項技術會很有用。 您可以修改範例以使用任何類型的結構化文字。
下列文字顯示 scores.csv的內容。 檔案代表電子表格數據。 第 1 欄是學生的標識碼,第 2 到 5 欄是測試分數。
111, 97, 92, 81, 60
112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91
下列文字顯示 names.csv的內容。 檔案代表包含學生姓氏、名字和學生標識符的電子錶格。
Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122
從包含相關資訊的不同檔案合併內容。 檔案 names.csv 包含學生名稱加上標識碼。 檔案 scores.csv 包含標識碼和一組四個測試分數。 下列查詢使用ID碼作為匹配鍵,將分數與學生名稱聯結起來。 下列範例顯示程式代碼:
string[] names = File.ReadAllLines(@"names.csv");
string[] scores = File.ReadAllLines(@"scores.csv");
var scoreQuery = from name in names
let nameFields = name.Split(',')
from id in scores
let scoreFields = id.Split(',')
where Convert.ToInt32(nameFields[2]) == Convert.ToInt32(scoreFields[0])
select $"{nameFields[0]},{scoreFields[1]},{scoreFields[2]},{scoreFields[3]},{scoreFields[4]}";
Console.WriteLine("\r\nMerge two spreadsheets:");
foreach (string item in scoreQuery)
{
Console.WriteLine(item);
}
Console.WriteLine($"{scoreQuery.Count()} total names in list");
/* Output:
Merge two spreadsheets:
Omelchenko, 97, 92, 81, 60
O'Donnell, 75, 84, 91, 39
Mortensen, 88, 94, 65, 91
Garcia, 97, 89, 85, 82
Garcia, 35, 72, 91, 70
Fakhouri, 99, 86, 90, 94
Feng, 93, 92, 80, 87
Garcia, 92, 90, 83, 78
Tucker, 68, 79, 88, 92
Adams, 99, 82, 81, 79
Zabokritski, 96, 85, 91, 60
Tucker, 94, 92, 91, 91
12 total names in list
*/
此範例示範如何在 .csv 檔案的數據行上執行匯總計算,例如 Sum、Average、Min 和 Max。 此處顯示的範例原則可以套用至其他類型的結構化文字。
下列文字顯示 scores.csv的內容。 假設第一個數據行代表學生標識符,而後續數據行代表來自四個測驗的分數。
111, 97, 92, 81, 60
112, 75, 84, 91, 39
113, 88, 94, 65, 91
114, 97, 89, 85, 82
115, 35, 72, 91, 70
116, 99, 86, 90, 94
117, 93, 92, 80, 87
118, 92, 90, 83, 78
119, 68, 79, 88, 92
120, 99, 82, 81, 79
121, 96, 85, 91, 60
122, 94, 92, 91, 91
下列文字示範如何使用 Split 方法,將每一行文字轉換成陣列。 陣列中的每個元素都代表一個欄。 最後,每個數據行中的文字都會轉換成其數值表示法。
public class SumColumns
{
public static void SumCSVColumns(string fileName)
{
string[] lines = File.ReadAllLines(fileName);
// Specifies the column to compute.
int exam = 3;
// Spreadsheet format:
// Student ID Exam#1 Exam#2 Exam#3 Exam#4
// 111, 97, 92, 81, 60
// Add one to exam to skip over the first column,
// which holds the student ID.
SingleColumn(lines, exam + 1);
Console.WriteLine();
MultiColumns(lines);
}
static void SingleColumn(IEnumerable<string> strs, int examNum)
{
Console.WriteLine("Single Column Query:");
// Parameter examNum specifies the column to
// run the calculations on. This value could be
// passed in dynamically at run time.
// Variable columnQuery is an IEnumerable<int>.
// The following query performs two steps:
// 1) use Split to break each row (a string) into an array
// of strings,
// 2) convert the element at position examNum to an int
// and select it.
var columnQuery = from line in strs
let elements = line.Split(',')
select Convert.ToInt32(elements[examNum]);
// Execute the query and cache the results to improve
// performance. This is helpful only with very large files.
var results = columnQuery.ToList();
// Perform aggregate calculations Average, Max, and
// Min on the column specified by examNum.
double average = results.Average();
int max = results.Max();
int min = results.Min();
Console.WriteLine($"Exam #{examNum}: Average:{average:##.##} High Score:{max} Low Score:{min}");
}
static void MultiColumns(IEnumerable<string> strs)
{
Console.WriteLine("Multi Column Query:");
// Create a query, multiColQuery. Explicit typing is used
// to make clear that, when executed, multiColQuery produces
// nested sequences. However, you get the same results by
// using 'var'.
// The multiColQuery query performs the following steps:
// 1) use Split to break each row (a string) into an array
// of strings,
// 2) use Skip to skip the "Student ID" column, and store the
// rest of the row in scores.
// 3) convert each score in the current row from a string to
// an int, and select that entire sequence as one row
// in the results.
var multiColQuery = from line in strs
let elements = line.Split(',')
let scores = elements.Skip(1)
select (from str in scores
select Convert.ToInt32(str));
// Execute the query and cache the results to improve
// performance.
// ToArray could be used instead of ToList.
var results = multiColQuery.ToList();
// Find out how many columns you have in results.
int columnCount = results[0].Count();
// Perform aggregate calculations Average, Max, and
// Min on each column.
// Perform one iteration of the loop for each column
// of scores.
// You can use a for loop instead of a foreach loop
// because you already executed the multiColQuery
// query by calling ToList.
for (int column = 0; column < columnCount; column++)
{
var results2 = from row in results
select row.ElementAt(column);
double average = results2.Average();
int max = results2.Max();
int min = results2.Min();
// Add one to column because the first exam is Exam #1,
// not Exam #0.
Console.WriteLine($"Exam #{column + 1} Average: {average:##.##} High Score: {max} Low Score: {min}");
}
}
}
/* Output:
Single Column Query:
Exam #4: Average:76.92 High Score:94 Low Score:39
Multi Column Query:
Exam #1 Average: 86.08 High Score: 99 Low Score: 35
Exam #2 Average: 86.42 High Score: 94 Low Score: 72
Exam #3 Average: 84.75 High Score: 91 Low Score: 65
Exam #4 Average: 76.92 High Score: 94 Low Score: 39
*/
如果檔案是索引標籤隔的檔案,只要將 方法中的 Split
自變數更新為 \t
。
訓練
模組
瞭解如何使用 System.IO 類別來管理本機檔案和目錄,以及如何使用 CSV 檔案和 StreamReader 和 StreamWriter 類別來儲存和擷取 C# 物件。