وبلاگ
آموزش کار با فایلها و مسیرها در C#: خواندن و نوشتن داده
فهرست مطالب
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان
0 تا 100 عطرسازی + (30 فرمولاسیون اختصاصی حامی صنعت)
دوره آموزش Flutter و برنامه نویسی Dart [پروژه محور]
دوره جامع آموزش برنامهنویسی پایتون + هک اخلاقی [با همکاری شاهک]
دوره جامع آموزش فرمولاسیون لوازم آرایشی
دوره جامع علم داده، یادگیری ماشین، یادگیری عمیق و NLP
دوره فوق فشرده مکالمه زبان انگلیسی (ویژه بزرگسالان)
شمع سازی و عودسازی با محوریت رایحه درمانی
صابون سازی (دستساز و صنعتی)
صفر تا صد طراحی دارو
متخصص طب سنتی و گیاهان دارویی
متخصص کنترل کیفی شرکت دارویی
آموزش کار با فایلها و مسیرها در C#: خواندن و نوشتن داده
در دنیای توسعه نرمافزار، توانایی تعامل با سیستم فایل یک مهارت اساسی و حیاتی است. برنامههای کاربردی به ندرت فقط با دادههای موجود در حافظه سروکار دارند؛ اغلب نیاز است تا دادهها را از فایلها بخوانند، تغییراتی در آنها ایجاد کنند و سپس نتایج را مجدداً در فایلها ذخیره کنند. این فرآیند، که به آن عملیات ورودی/خروجی (I/O) فایل گفته میشود، برای طیف وسیعی از کاربردها، از ذخیرهسازی پیکربندیها و لاگفایلها گرفته تا پردازش مجموعه دادههای عظیم و مدیریت اسناد، ضروری است.
C# به عنوان یک زبان برنامهنویسی قدرتمند و چندمنظوره در چارچوب .NET، ابزارها و کلاسهای بسیار غنی و کارآمدی را برای مدیریت فایلها و مسیرها ارائه میدهد. فضای نام System.IO
در .NET Framework و .NET Core، مجموعهای جامع از کلاسها را فراهم میکند که امکان انجام هرگونه عملیات مرتبط با فایل سیستم را به برنامهنویس میدهد. این کلاسها نه تنها خواندن و نوشتن دادههای متنی و باینری را ساده میکنند، بلکه امکان مدیریت دایرکتوریها، دستکاری مسیرها و حتی انجام عملیات ناهمگام (asynchronous) را برای بهبود پاسخگویی برنامهها فراهم میآورند.
هدف این مقاله، ارائه یک راهنمای جامع و تخصصی برای کار با فایلها و مسیرها در C# است. ما از مفاهیم اولیه سیستم فایل و انواع مسیرها شروع کرده، به بررسی عمیق کلاسهای اصلی در System.IO
خواهیم پرداخت. سپس، چگونگی خواندن و نوشتن انواع دادهها (متنی و باینری) را با مثالهای کاربردی مورد بحث قرار خواهیم داد. همچنین، مباحث پیشرفتهتری مانند مدیریت مسیرها و دایرکتوریها، عملیات I/O ناهمگام و بهترین شیوهها برای مدیریت خطا و بهبود عملکرد را پوشش خواهیم داد. با مطالعه این مقاله، شما نه تنها با اصول اولیه آشنا میشوید، بلکه دانش و مهارت لازم برای پیادهسازی راهحلهای پایدار و کارآمد برای تعامل با سیستم فایل در پروژههای C# خود را به دست خواهید آورد.
مبانی سیستم فایل و فضای نام System.IO
پیش از ورود به جزئیات عملیات خواندن و نوشتن، درک مفاهیم بنیادی سیستم فایل و آشنایی با فضای نام System.IO
که حاوی تمام ابزارهای مورد نیاز ماست، از اهمیت بالایی برخوردار است. سیستم فایل، ساختاری است که سیستم عامل برای سازماندهی و ذخیرهسازی دادهها در یک رسانه ذخیرهسازی (مانند هارد دیسک) استفاده میکند. این ساختار شامل فایلها و دایرکتوریها (پوشهها) است که هر کدام دارای مسیر منحصر به فردی برای دسترسی هستند.
انواع مسیرها: مطلق و نسبی
برای اشاره به مکان یک فایل یا دایرکتوری، از مسیر (Path) استفاده میشود. مسیرها به دو دسته اصلی تقسیم میشوند:
- مسیر مطلق (Absolute Path): یک مسیر مطلق، مکان کامل و دقیق یک فایل یا دایرکتوری را از ریشه سیستم فایل مشخص میکند. این نوع مسیر مستقل از مکان فعلی اجرای برنامه است و همیشه به یک مکان خاص اشاره دارد.
مثال (ویندوز):C:\Users\username\Documents\MyFile.txt
مثال (لینوکس/macOS):/home/username/documents/MyFile.txt
- مسیر نسبی (Relative Path): یک مسیر نسبی، مکان یک فایل یا دایرکتوری را نسبت به یک نقطه مرجع (معمولاً دایرکتوری فعلی اجرای برنامه) مشخص میکند. این نوع مسیر کوتاهتر است اما به مکان اجرای برنامه وابسته است.
مثال:MyFolder\MyFile.txt
(به معنایMyFile.txt
درونMyFolder
در دایرکتوری فعلی)
مثال:..\..\OtherFolder\AnotherFile.txt
(به معنای رفتن دو سطح به بالا و سپس ورود بهOtherFolder
)
انتخاب بین مسیر مطلق و نسبی به سناریوی کاربردی بستگی دارد. مسیرهای مطلق برای دسترسی به مکانهای ثابت و مشخص (مانند یک پوشه مشترک در شبکه) مناسب هستند، در حالی که مسیرهای نسبی برای فایلهای همراه برنامه یا در ساختارهای دایرکتوری محلی کاربرد دارند.
کلاسهای کلیدی در System.IO
فضای نام System.IO
هسته اصلی تعامل C# با سیستم فایل است. این فضای نام شامل کلاسهای متعددی است که هر یک وظایف خاصی را بر عهده دارند. در ادامه به معرفی مهمترین آنها میپردازیم:
-
File
کلاس: این کلاس استاتیک، مجموعهای از متدهای کمکی را برای عملیات رایج روی فایلها ارائه میدهد. این متدها برای کارهای سادهای مانند خواندن تمام متن یک فایل، نوشتن تمام خطوط، کپی کردن، حذف، جابجایی و بررسی وجود یک فایل بسیار مفید هستند. این کلاس اغلب برای عملیات سریع و بدون نیاز به کنترل دقیق روی جریان داده استفاده میشود.// مثال: خواندن تمام متن یک فایل string content = File.ReadAllText("MyFile.txt"); // مثال: کپی کردن یک فایل File.Copy("Source.txt", "Destination.txt", true); // true برای بازنویسی
-
Directory
کلاس: مشابه کلاسFile
، این کلاس نیز استاتیک است و متدهایی برای مدیریت دایرکتوریها (پوشهها) فراهم میکند. این متدها شامل ایجاد، حذف، جابجایی، کپی کردن و بررسی وجود دایرکتوریها و همچنین لیست کردن فایلها و دایرکتوریهای درون یک مسیر مشخص میشوند.// مثال: ایجاد یک دایرکتوری جدید Directory.CreateDirectory("NewFolder"); // مثال: بررسی وجود یک دایرکتوری bool exists = Directory.Exists("Logs");
-
Path
کلاس: این کلاس استاتیک برای انجام عملیات روی رشتههای مسیر (Path strings) طراحی شده است. از آنجا که مسیرها میتوانند شامل کاراکترهای خاص سیستم عامل باشند (مانانند\
در ویندوز و/
در لینوکس)، استفاده از کلاسPath
برای ترکیب، تجزیه و اعتبارسنجی مسیرها بسیار حیاتی است. این کلاس تضمین میکند که کدهای شما بین پلتفرمهای مختلف قابل حمل باشند.// مثال: ترکیب اجزای مسیر string fullPath = Path.Combine("C:\\", "Users", "Documents", "MyFile.txt"); // مثال: گرفتن نام فایل از یک مسیر کامل string fileName = Path.GetFileName(fullPath); // MyFile.txt
-
FileStream
کلاس: این کلاس برای خواندن و نوشتن بایتها از و به فایلها استفاده میشود.FileStream
یک جریان (stream) از بایتها را نمایش میدهد و برای کار با فایلهای باینری (مانند تصاویر، ویدئوها، فایلهای اجرایی) یا زمانی که نیاز به کنترل دقیق بر روی نحوه خواندن/نوشتن (مثلاً موقعیت در فایل) دارید، ایدهآل است. این کلاس پایه بسیاری از کلاسهای دیگر جریانمحور است.// مثال: ایجاد یک FileStream برای نوشتن using (FileStream fs = new FileStream("BinaryData.bin", FileMode.Create)) { byte[] data = { 0x01, 0x02, 0x03, 0x04 }; fs.Write(data, 0, data.Length); }
-
StreamReader
کلاس: این کلاس برای خواندن کاراکترها از یک جریان (معمولاًFileStream
) با یک کدگذاری خاص استفاده میشود.StreamReader
ابزاری عالی برای خواندن فایلهای متنی است، زیرا به طور خودکار تبدیل بایتها به کاراکترها را با توجه به کدگذاری مشخص شده (مانند UTF-8) انجام میدهد.// مثال: خواندن خط به خط از یک فایل متنی using (StreamReader sr = new StreamReader("Log.txt")) { string line; while ((line = sr.ReadLine()) != null) { Console.WriteLine(line); } }
-
StreamWriter
کلاس: مکملStreamReader
، این کلاس برای نوشتن کاراکترها به یک جریان (معمولاًFileStream
) با یک کدگذاری خاص استفاده میشود.StreamWriter
برای نوشتن فایلهای متنی، از جمله اضافه کردن متن به یک فایل موجود یا ایجاد فایلهای جدید، بسیار مفید است.// مثال: نوشتن متن به یک فایل using (StreamWriter sw = new StreamWriter("Output.txt")) { sw.WriteLine("Hello, World!"); sw.Write("This is another line."); }
-
BinaryReader
وBinaryWriter
کلاسها: این کلاسها برای خواندن و نوشتن دادههای باینری از و به جریانها به صورت تایپشده (typed) طراحی شدهاند. به جای کار با آرایههای بایت خام، میتوانید مستقیماً انواع دادههای اولیه (مانند int، float، string) را بخوانید و بنویسید. این کار فرآیند سریالیسازی/دیسریالیسازی دادههای باینری را ساده میکند.// مثال: نوشتن داده باینری تایپشده using (FileStream fs = new FileStream("Data.bin", FileMode.Create)) using (BinaryWriter bw = new BinaryWriter(fs)) { bw.Write(123); // int bw.Write("Hello"); // string bw.Write(true); // bool }
درک نقش و کاربرد هر یک از این کلاسها، سنگ بنای انجام هرگونه عملیات I/O فایل در C# است. در بخشهای بعدی، به تفصیل نحوه استفاده از آنها را با مثالهای عملی بررسی خواهیم کرد.
خواندن داده از فایلها در C#
عملیات خواندن از فایلها یکی از رایجترین عملیات I/O است. بسته به نوع فایل (متنی یا باینری) و حجم داده، روشهای مختلفی برای انجام این کار در C# وجود دارد. در این بخش، به بررسی جامع این روشها میپردازیم.
خواندن فایلهای متنی
فایلهای متنی (مانند .txt, .log, .csv, .json, .xml) حاوی کاراکترهایی هستند که با یک کدگذاری خاص (مثلاً UTF-8، ASCII، Latin-1) ذخیره شدهاند. C# چندین راهکار برای خواندن این فایلها ارائه میدهد:
۱. استفاده از کلاس File برای خواندن سریع
کلاس File
متدهای استاتیک سادهای را برای خواندن کامل یک فایل متنی فراهم میکند که برای فایلهای کوچک تا متوسط بسیار مناسب هستند. این متدها کل فایل را به یکباره در حافظه بارگذاری میکنند.
-
File.ReadAllText(path, [encoding])
: تمام متن یک فایل را به صورت یک رشته واحد میخواند.string filePath = "MyDocument.txt"; try { string fileContent = File.ReadAllText(filePath); Console.WriteLine("Content of the file:\n" + fileContent); } catch (FileNotFoundException) { Console.WriteLine($"Error: File '{filePath}' not found."); } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
-
File.ReadAllLines(path, [encoding])
: تمام خطوط یک فایل را میخواند و آنها را به صورت یک آرایه از رشتهها برمیگرداند که هر عنصر آرایه یک خط از فایل است.string filePath = "LogFile.txt"; try { string[] lines = File.ReadAllLines(filePath); Console.WriteLine("Lines in the file:"); foreach (string line in lines) { Console.WriteLine(line); } } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
نکته مهم: این متدها برای فایلهای بسیار بزرگ توصیه نمیشوند، زیرا کل محتوای فایل را در حافظه بارگذاری میکنند که میتواند منجر به مصرف زیاد حافظه (OutOfMemoryException) شود.
۲. استفاده از StreamReader برای خواندن جریانمحور
برای فایلهای بزرگتر یا زمانی که نیاز به پردازش خط به خط یا بلوک به بلوک دارید، StreamReader
ابزار قدرتمندتری است. StreamReader
بر روی یک FileStream
کار میکند و امکان خواندن کاراکترها با یک کدگذاری خاص را فراهم میکند. استفاده از بلوک using
با StreamReader
بسیار مهم است تا منابع فایل به درستی آزاد شوند.
-
خواندن خط به خط: این روش کارآمدترین راه برای خواندن فایلهای متنی بزرگ است، زیرا فقط یک خط در هر زمان در حافظه نگهداری میشود.
string filePath = "LargeData.csv"; try { using (StreamReader sr = new StreamReader(filePath)) { string line; while ((line = sr.ReadLine()) != null) { // اینجا میتوانید هر خط را پردازش کنید Console.WriteLine($"Processing line: {line}"); } } } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
-
خواندن بلوک به بلوک (کاراکتر به کاراکتر): برای کنترل دقیقتر یا زمانی که ساختار فایل خطمحور نیست.
string filePath = "Config.xml"; try { using (StreamReader sr = new StreamReader(filePath)) { char[] buffer = new char[1024]; // بافر 1KB int charsRead; while ((charsRead = sr.Read(buffer, 0, buffer.Length)) > 0) { string chunk = new string(buffer, 0, charsRead); Console.Write(chunk); // نمایش بلوک خوانده شده } } } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
-
خواندن تمام محتوا (اما با کنترل
StreamReader
):string filePath = "SmallText.txt"; try { using (StreamReader sr = new StreamReader(filePath)) { string content = sr.ReadToEnd(); Console.WriteLine(content); } } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
اگرچه
ReadToEnd()
کل فایل را میخواند، اما مزیت استفاده ازStreamReader
این است که شما کنترل دقیقتری بر روی کدگذاری دارید و میتوانید آن را سفارشی کنید.
خواندن فایلهای باینری
فایلهای باینری (مانند تصاویر، فایلهای صوتی، فایلهای فشرده، فایلهای اجرایی) به صورت جریانی از بایتها ذخیره میشوند و نمیتوان آنها را مستقیماً به عنوان متن خواند. برای کار با این نوع فایلها، از FileStream
(برای خواندن بایتهای خام) و BinaryReader
(برای خواندن انواع دادههای تایپشده) استفاده میشود.
۱. استفاده از FileStream برای بایتهای خام
FileStream
اجازه میدهد تا دادهها را به صورت آرایههای بایت بخوانید. این روش برای کپی کردن فایلها یا پردازش بایت به بایت مفید است.
string sourceFilePath = "image.png";
string destinationFilePath = "image_copy.png";
try
{
using (FileStream sourceStream = new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read))
using (FileStream destinationStream = new FileStream(destinationFilePath, FileMode.Create, FileAccess.Write))
{
byte[] buffer = new byte[4096]; // بافر 4KB
int bytesRead;
while ((bytesRead = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
{
destinationStream.Write(buffer, 0, bytesRead);
}
Console.WriteLine("Image copied successfully.");
}
}
catch (FileNotFoundException)
{
Console.WriteLine($"Error: Source file '{sourceFilePath}' not found.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
۲. استفاده از BinaryReader برای دادههای تایپشده
BinaryReader
بر روی یک FileStream
کار میکند و امکان خواندن انواع دادههای اولیه C# (مانند int, float, string) را به صورت مستقیم فراهم میکند، بدون اینکه نیاز به تبدیل دستی از بایتها باشد. این کار برای ذخیره و بازیابی ساختارهای داده ساده در فایلهای باینری بسیار مفید است.
string dataFilePath = "MyBinaryData.dat";
// فرض کنید قبلاً این فایل را ایجاد کردهایم با BinaryWriter
// for example:
// using (FileStream fs = new FileStream(dataFilePath, FileMode.Create))
// using (BinaryWriter bw = new BinaryWriter(fs))
// {
// bw.Write(10); // int
// bw.Write(3.14f); // float
// bw.Write("Hello"); // string
// bw.Write(true); // bool
// }
try
{
using (FileStream fs = new FileStream(dataFilePath, FileMode.Open, FileAccess.Read))
using (BinaryReader br = new BinaryReader(fs))
{
int intValue = br.ReadInt32();
float floatValue = br.ReadSingle();
string stringValue = br.ReadString();
bool boolValue = br.ReadBoolean();
Console.WriteLine($"Read Int: {intValue}");
Console.WriteLine($"Read Float: {floatValue}");
Console.WriteLine($"Read String: {stringValue}");
Console.WriteLine($"Read Bool: {boolValue}");
}
}
catch (FileNotFoundException)
{
Console.WriteLine($"Error: File '{dataFilePath}' not found.");
}
catch (EndOfStreamException)
{
Console.WriteLine("Error: Reached end of file unexpectedly. File might be corrupted or in an unexpected format.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
مدیریت کدگذاری (Encoding)
یکی از جنبههای حیاتی هنگام کار با فایلهای متنی، مدیریت کدگذاری کاراکتر است. کدگذاری تعیین میکند که چگونه کاراکترها به بایتها تبدیل شوند و بالعکس. اگر فایل را با کدگذاری اشتباه بخوانید، ممکن است کاراکترها به درستی نمایش داده نشوند (مثلاً ? یا کاراکترهای نامفهوم). کلاس Encoding
در فضای نام System.Text
مجموعهای از کدگذاریهای استاندارد را فراهم میکند:
Encoding.UTF8
(رایجترین و توصیه شده)Encoding.Unicode
(UTF-16)Encoding.ASCII
Encoding.Default
(کدگذاری پیشفرض سیستم عامل)
هنگام استفاده از StreamReader
یا متدهای File.ReadAllText
/ReadAllLines
، میتوانید کدگذاری را به عنوان آرگومان دوم مشخص کنید:
// خواندن فایل با کدگذاری UTF-8
using (StreamReader sr = new StreamReader("MyFile.txt", Encoding.UTF8))
{
string content = sr.ReadToEnd();
Console.WriteLine(content);
}
// خواندن فایل با کدگذاری Latin-1 (ISO-8859-1)
string latin1Content = File.ReadAllText("AnotherFile.txt", Encoding.GetEncoding("ISO-8859-1"));
Console.WriteLine(latin1Content);
همیشه سعی کنید از UTF-8
برای فایلهای متنی خود استفاده کنید، زیرا این کدگذاری از طیف وسیعی از کاراکترها پشتیبانی میکند و به طور گستردهای سازگار است.
نوشتن داده در فایلها در C#
نوشتن داده در فایلها نیز به اندازه خواندن آنها مهم است. در C#، میتوانید دادههای متنی یا باینری را در فایلها ذخیره کنید، چه برای ایجاد فایلهای جدید و چه برای اضافه کردن به فایلهای موجود. مانند خواندن، بهترین روش نوشتن به نوع داده و حجم آن بستگی دارد.
نوشتن در فایلهای متنی
برای نوشتن اطلاعات متنی در فایلها، C# چندین گزینه را فراهم میکند:
۱. استفاده از کلاس File برای نوشتن سریع
متدهای استاتیک کلاس File
برای نوشتن سریع و راحت در فایلهای متنی، به خصوص برای فایلهای کوچک، ایدهآل هستند. این متدها فایل را باز کرده، داده را مینویسند و بلافاصله میبندند.
-
File.WriteAllText(path, contents, [encoding])
: تمام رشته مشخص شده را در یک فایل مینویسد. اگر فایل وجود نداشته باشد، آن را ایجاد میکند؛ اگر وجود داشته باشد، محتوای آن را بازنویسی (overwrite) میکند.string filePath = "NewDocument.txt"; string content = "This is the first line.\nThis is the second line."; try { File.WriteAllText(filePath, content); Console.WriteLine($"Successfully wrote to '{filePath}'."); } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
-
File.WriteAllLines(path, contents, [encoding])
: یک مجموعه از رشتهها (مثلاًstring[]
یاList
) را میگیرد و هر رشته را به عنوان یک خط جداگانه در فایل مینویسد.string filePath = "MyLog.txt"; string[] logEntries = { "2023-10-27 10:00:00 - Application started.", "2023-10-27 10:05:15 - User 'admin' logged in.", "2023-10-27 10:10:30 - Data processed successfully." }; try { File.WriteAllLines(filePath, logEntries); Console.WriteLine($"Successfully wrote log entries to '{filePath}'."); } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
-
File.AppendAllText(path, contents, [encoding])
: رشته مشخص شده را به انتهای یک فایل موجود اضافه میکند. اگر فایل وجود نداشته باشد، آن را ایجاد میکند.string filePath = "DailyReport.txt"; string newEntry = "\n2023-10-27 - Report generated."; // Add a newline before the new entry try { File.AppendAllText(filePath, newEntry); Console.WriteLine($"Successfully appended to '{filePath}'."); } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
-
File.AppendAllLines(path, contents, [encoding])
: مجموعهای از خطوط را به انتهای یک فایل موجود اضافه میکند.string filePath = "ActivityLog.txt"; List<string> newActivities = new List<string> { "User A performed action X.", "User B performed action Y." }; try { File.AppendAllLines(filePath, newActivities); Console.WriteLine($"Successfully appended new activities to '{filePath}'."); } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); }
۲. استفاده از StreamWriter برای نوشتن جریانمحور
برای فایلهای بزرگتر، نوشتن تدریجی یا زمانی که نیاز به کنترل دقیق بر روی جریان نوشتن دارید (مانند نگهداری فایل باز برای نوشتن مکرر)، StreamWriter
بهترین انتخاب است. StreamWriter
بر روی یک FileStream
کار میکند و امکان نوشتن کاراکترها با یک کدگذاری خاص را فراهم میکند. استفاده از using
برای اطمینان از بسته شدن صحیح StreamWriter
و FileStream
ضروری است.
string filePath = "LargeOutput.txt";
try
{
// FileMode.Create: فایل را ایجاد میکند یا اگر وجود دارد، آن را بازنویسی میکند.
// FileMode.Append: اگر فایل وجود دارد، به انتهای آن اضافه میکند؛ در غیر این صورت، آن را ایجاد میکند.
using (StreamWriter sw = new StreamWriter(filePath, append: true, encoding: Encoding.UTF8)) // append: true for appending
{
sw.WriteLine("This is a dynamically generated line.");
sw.Write("This is part of the same line, ");
sw.Write("but without a newline at the end.\n"); // Manually add newline
for (int i = 0; i < 10; i++)
{
sw.WriteLine($"Log entry {i:D3}"); // Formatted output
}
}
Console.WriteLine($"Content written to '{filePath}' using StreamWriter.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
StreamWriter
دارای متدهایی مانند Write()
(برای نوشتن بدون اضافه کردن خط جدید) و WriteLine()
(برای نوشتن با اضافه کردن خط جدید) است.
نوشتن در فایلهای باینری
برای ذخیره دادهها به صورت باینری (مثلاً آرایهای از بایتها، یا انواع دادههای تایپشده)، از FileStream
و BinaryWriter
استفاده میکنیم.
۱. استفاده از FileStream برای بایتهای خام
اگر نیاز به نوشتن آرایهای از بایتها به صورت مستقیم در یک فایل دارید، FileStream
برای این کار مناسب است. این روش برای کپی کردن فایلها یا ذخیره دادههای خام (مانند پیکسلهای یک تصویر) استفاده میشود.
string outputFilePath = "output.bin";
byte[] rawData = { 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; // Example raw bytes
try
{
using (FileStream fs = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write))
{
fs.Write(rawData, 0, rawData.Length);
Console.WriteLine($"Successfully wrote raw bytes to '{outputFilePath}'.");
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
۲. استفاده از BinaryWriter برای دادههای تایپشده
BinaryWriter
به شما اجازه میدهد تا انواع دادههای اولیه C# را مستقیماً به یک جریان باینری بنویسید. این کار فرآیند ذخیره و بازیابی دادههای ساختاریافته را به صورت باینری بسیار ساده میکند.
string binaryDataFilePath = "StructuredData.dat";
try
{
using (FileStream fs = new FileStream(binaryDataFilePath, FileMode.Create, FileAccess.Write))
using (BinaryWriter bw = new BinaryWriter(fs))
{
bw.Write(12345); // int
bw.Write(987.65f); // float
bw.Write("My Data String"); // string
bw.Write(DateTime.Now.ToBinary()); // DateTime as long
bw.Write(false); // bool
Console.WriteLine($"Successfully wrote structured binary data to '{binaryDataFilePath}'.");
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
هنگام خواندن این دادهها با BinaryReader
، باید اطمینان حاصل کنید که متدهای خواندن را به همان ترتیبی که نوشتهاید، فراخوانی میکنید تا دادهها به درستی بازیابی شوند.
مانند خواندن فایلها، هنگام نوشتن فایلهای متنی نیز میتوانید کدگذاری را مشخص کنید تا از سازگاری و صحت نمایش کاراکترها اطمینان حاصل شود. به طور پیشفرض، StreamWriter
از Encoding.UTF8
بدون BOM (Byte Order Mark) استفاده میکند.
مدیریت مسیرها و دایرکتوریها
علاوه بر خواندن و نوشتن فایلها، مدیریت مسیرها و دایرکتوریها (پوشهها) جزء جداییناپذیر برنامهنویسی فایل I/O است. C# با کلاسهای Path
و Directory
ابزارهای قدرتمندی برای این منظور ارائه میدهد.
کلاس Path: ابزاری قدرتمند برای مسیردهی
کلاس Path
یک کلاس استاتیک در فضای نام System.IO
است که برای انجام عملیات بر روی رشتههای مسیر طراحی شده است. این کلاس از کاراکترهای جداکننده مسیر (\
در ویندوز، /
در لینوکس/macOS) به صورت هوشمندانه استفاده میکند و کدهای شما را قابل حمل بین پلتفرمهای مختلف میسازد. هرگز خودتان رشتههای مسیر را با +
یا string.Format
ترکیب نکنید، بلکه همیشه از Path.Combine
استفاده کنید.
-
Path.Combine(string[] paths)
: اجزای یک مسیر را به صورت صحیح ترکیب میکند. این مهمترین متد در کلاسPath
است.string folder = "MyApplicationData"; string subFolder = "Logs"; string fileName = "Errors.log"; string fullPath = Path.Combine(Environment.CurrentDirectory, folder, subFolder, fileName); // Example output on Windows: C:\MyProject\bin\Debug\MyApplicationData\Logs\Errors.log // Example output on Linux: /home/user/MyProject/bin/Debug/MyApplicationData/Logs/Errors.log Console.WriteLine($"Combined path: {fullPath}"); // ترکیب با مسیر مطلق string absolutePath = Path.Combine("C:\\", "Program Files", "MyApp", "settings.json"); Console.WriteLine($"Absolute combined path: {absolutePath}");
-
Path.GetFileName(string path)
: نام فایل (شامل پسوند) را از یک مسیر کامل برمیگرداند.string fileName = Path.GetFileName("C:\\Folder\\SubFolder\\MyFile.txt"); // MyFile.txt Console.WriteLine($"File Name: {fileName}");
-
Path.GetFileNameWithoutExtension(string path)
: نام فایل را بدون پسوند برمیگرداند.string fileNameWithoutExt = Path.GetFileNameWithoutExtension("C:\\Folder\\MyImage.jpeg"); // MyImage Console.WriteLine($"File Name Without Extension: {fileNameWithoutExt}");
-
Path.GetExtension(string path)
: پسوند فایل (شامل نقطه) را برمیگرداند.string extension = Path.GetExtension("C:\\Folder\\MyDoc.docx"); // .docx Console.WriteLine($"Extension: {extension}");
-
Path.GetDirectoryName(string path)
: مسیر دایرکتوری والد را از یک مسیر کامل برمیگرداند.string directoryName = Path.GetDirectoryName("C:\\Folder\\SubFolder\\File.txt"); // C:\Folder\SubFolder Console.WriteLine($"Directory Name: {directoryName}");
-
Path.GetFullPath(string path)
: یک مسیر نسبی را به مسیر مطلق تبدیل میکند.string relativePath = "Data\\report.csv"; string fullAbsolutePath = Path.GetFullPath(relativePath); Console.WriteLine($"Full Absolute Path: {fullAbsolutePath}");
-
Path.ChangeExtension(string path, string newExtension)
: پسوند یک فایل را تغییر میدهد.string newPath = Path.ChangeExtension("MyFile.txt", ".pdf"); // MyFile.pdf Console.WriteLine($"Changed Extension: {newPath}");
-
Path.GetTempPath()
: مسیر دایرکتوری فایلهای موقت سیستم را برمیگرداند.string tempPath = Path.GetTempPath(); Console.WriteLine($"Temp Path: {tempPath}");
-
Path.GetTempFileName()
: یک نام فایل موقت یکتا در دایرکتوری فایلهای موقت ایجاد میکند.string tempFile = Path.GetTempFileName(); Console.WriteLine($"Temp File: {tempFile}"); File.Delete(tempFile); // فراموش نکنید فایل موقت را حذف کنید
کلاس Directory: عملیات روی پوشهها
کلاس Directory
نیز یک کلاس استاتیک است که متدهایی برای ایجاد، حذف، جابجایی و لیست کردن دایرکتوریها ارائه میدهد. این کلاس برای مدیریت ساختار پوشههای برنامه شما بسیار مفید است.
-
Directory.CreateDirectory(string path)
: یک دایرکتوری و تمام دایرکتوریهای والد لازم را در صورت عدم وجود ایجاد میکند.string newDirPath = "AppData\\Logs\\System"; try { Directory.CreateDirectory(newDirPath); Console.WriteLine($"Directory '{newDirPath}' created."); } catch (Exception ex) { Console.WriteLine($"Error creating directory: {ex.Message}"); }
-
Directory.Exists(string path)
: بررسی میکند که آیا یک دایرکتوری در مسیر مشخص شده وجود دارد یا خیر.if (Directory.Exists("C:\\Program Files")) { Console.WriteLine("Program Files directory exists."); }
-
Directory.Delete(string path, bool recursive)
: یک دایرکتوری را حذف میکند. اگرrecursive
برابر باtrue
باشد، تمام زیرپوشهها و فایلهای درون آن نیز حذف میشوند.string dirToDelete = "OldData"; Directory.CreateDirectory(dirToDelete); // Make sure it exists for demo File.WriteAllText(Path.Combine(dirToDelete, "temp.txt"), "delete me"); try { // Delete with recursive: true to delete content as well Directory.Delete(dirToDelete, true); Console.WriteLine($"Directory '{dirToDelete}' deleted."); } catch (DirectoryNotFoundException) { Console.WriteLine($"Directory '{dirToDelete}' not found."); } catch (Exception ex) { Console.WriteLine($"Error deleting directory: {ex.Message}"); }
-
Directory.Move(string sourceDirName, string destDirName)
: یک دایرکتوری و محتویات آن را به مکان جدیدی منتقل میکند.string sourceDir = "TempReports"; string destDir = "Archive\\Reports_2023"; Directory.CreateDirectory(sourceDir); // Ensure source exists Directory.CreateDirectory("Archive"); // Ensure parent of destination exists File.WriteAllText(Path.Combine(sourceDir, "report1.txt"), "data"); try { Directory.Move(sourceDir, destDir); Console.WriteLine($"Directory '{sourceDir}' moved to '{destDir}'."); } catch (Exception ex) { Console.WriteLine($"Error moving directory: {ex.Message}"); }
-
Directory.GetFiles(string path, [string searchPattern], [SearchOption searchOption])
: لیستی از مسیر فایلها را در یک دایرکتوری برمیگرداند. -
Directory.GetDirectories(string path, [string searchPattern], [SearchOption searchOption])
: لیستی از مسیر زیردایرکتوریها را در یک دایرکتوری برمیگرداند.string currentDir = "."; // Current directory Console.WriteLine($"Files in {currentDir}:"); foreach (string file in Directory.GetFiles(currentDir, "*.txt")) // Only .txt files { Console.WriteLine(Path.GetFileName(file)); } Console.WriteLine($"Subdirectories in {currentDir}:"); foreach (string dir in Directory.GetDirectories(currentDir)) { Console.WriteLine(Path.GetFileName(dir)); } // SearchOption.AllDirectories برای جستجوی بازگشتی در زیرپوشهها Console.WriteLine("All .cs files recursively:"); foreach (string csFile in Directory.GetFiles(currentDir, "*.cs", SearchOption.AllDirectories)) { Console.WriteLine(csFile); }
استفاده صحیح از کلاسهای Path
و Directory
به شما کمک میکند تا کدی تمیزتر، قویتر و قابل حملتر بنویسید که به خوبی با ساختار سیستم فایل تعامل داشته باشد.
عملیات ورودی/خروجی ناهمگام (Asynchronous I/O) در C#
در برنامههای مدرن، پاسخگو بودن رابط کاربری (UI) و کارایی کلی سیستم بسیار حیاتی است. عملیات I/O (مانند خواندن/نوشتن فایل) ذاتاً کند هستند، زیرا شامل تعامل با سختافزار (دیسک) میشوند. اگر این عملیات به صورت همگام (synchronous) در رشته اصلی (main thread) اجرا شوند، میتوانند باعث بلوکه شدن برنامه و عدم پاسخگویی آن شوند. برای حل این مشکل، C# و .NET از مدل برنامهنویسی ناهمگام با استفاده از کلمات کلیدی async
و await
پشتیبانی میکنند.
مزایای I/O ناهمگام
استفاده از عملیات I/O ناهمگام چندین مزیت کلیدی دارد:
- بهبود پاسخگویی UI: در برنامههای دسکتاپ (WPF, WinForms) یا وب (ASP.NET)، اجرای عملیات فایل در رشته پسزمینه تضمین میکند که UI یا سرور میتواند به تعاملات کاربر یا درخواستهای دیگر پاسخ دهد، حتی در حالی که عملیات I/O طولانی در حال انجام است.
- استفاده بهینه از منابع: به جای اینکه یک رشته منتظر تکمیل عملیات I/O بماند (که در این حالت رشته بیکار و منابع آن در حال استفاده بیحاصل هستند)، با I/O ناهمگام، رشته آزاد میشود تا کارهای دیگر را انجام دهد. هنگامی که عملیات I/O کامل شد، کنترل دوباره به رشته (یا یک رشته از Thread Pool) برمیگردد تا ادامه کار را انجام دهد. این امر به ویژه در سناریوهای سرور با تعداد زیادی درخواست همزمان (مانند ASP.NET Core) بسیار مهم است.
- کدنویسی سادهتر: با
async
/await
، کد ناهمگام بسیار شبیه به کد همگام به نظر میرسد، که خوانایی و نگهداری آن را آسانتر میکند.
پیادهسازی با async/await
بسیاری از کلاسهای I/O در System.IO
(به ویژه آنهایی که از Stream
ارث میبرند) متدهای ناهمگام معادل خود را دارند که با پسوند Async
مشخص میشوند (مثلاً ReadAsync
, WriteAsync
, CopyToAsync
, ReadToEndAsync
, WriteLineAsync
). برای استفاده از آنها، متد فراخواننده باید با کلمه کلیدی async
مشخص شده و از await
برای انتظار نتایج استفاده شود.
خواندن ناهمگام از فایل متنی
برای خواندن یک فایل متنی بزرگ به صورت ناهمگام، میتوانید از StreamReader.ReadToEndAsync()
یا StreamReader.ReadLineAsync()
استفاده کنید.
public static async Task ReadFileAsync(string filePath)
{
try
{
// ReadAllTextAsync is a convenience method from File class for async reading
string content = await File.ReadAllTextAsync(filePath);
Console.WriteLine("File content read asynchronously (first 100 chars):\n" + content.Substring(0, Math.Min(content.Length, 100)));
}
catch (FileNotFoundException)
{
Console.WriteLine($"Error: File '{filePath}' not found.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
Console.WriteLine("-----------------------------------");
// Using StreamReader for line-by-line async reading
try
{
using (StreamReader reader = new StreamReader(filePath))
{
string line;
int lineCount = 0;
while ((line = await reader.ReadLineAsync()) != null && lineCount < 5) // Read first 5 lines for demo
{
Console.WriteLine($"Async Line {++lineCount}: {line}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred during line-by-line async read: {ex.Message}");
}
}
// How to call it from main method (or any other async method)
// static async Task Main(string[] args)
// {
// await ReadFileAsync("LargeTextFile.txt");
// Console.WriteLine("Finished async file reading operation.");
// }
نوشتن ناهمگام در فایل متنی
برای نوشتن ناهمگام در فایلهای متنی، میتوانید از StreamWriter.WriteAsync()
یا StreamWriter.WriteLineAsync()
استفاده کنید. همچنین، متدهای File.WriteAllTextAsync()
و File.AppendAllTextAsync()
نیز برای سادگی در دسترس هستند.
public static async Task WriteFileAsync(string filePath, IEnumerable<string> lines)
{
try
{
// Using File.WriteAllLinesAsync for simplicity
await File.WriteAllLinesAsync(filePath, lines);
Console.WriteLine($"Successfully wrote all lines asynchronously to '{filePath}'.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred during async write: {ex.Message}");
}
Console.WriteLine("-----------------------------------");
// Using StreamWriter for more controlled async writing/appending
try
{
using (StreamWriter writer = new StreamWriter(filePath, append: true, encoding: Encoding.UTF8))
{
await writer.WriteLineAsync("This line was appended asynchronously.");
await writer.WriteLineAsync("Another async appended line.");
}
Console.WriteLine($"Successfully appended lines asynchronously to '{filePath}' using StreamWriter.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred during async append with StreamWriter: {ex.Message}");
}
}
// How to call it
// static async Task Main(string[] args)
// {
// List<string> dataToWrite = new List<string>
// {
// "Data line 1",
// "Data line 2",
// "Data line 3"
// };
// await WriteFileAsync("OutputAsync.txt", dataToWrite);
// }
کپی ناهمگام فایل باینری
برای عملیات باینری بزرگ مانند کپی کردن فایلها، Stream.CopyToAsync()
بسیار مفید است و عملیات کپی را به صورت ناهمگام انجام میدهد.
public static async Task CopyFileAsync(string sourcePath, string destinationPath)
{
try
{
using (FileStream sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true)) // true for async I/O
using (FileStream destinationStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) // true for async I/O
{
await sourceStream.CopyToAsync(destinationStream);
Console.WriteLine($"File '{sourcePath}' copied asynchronously to '{destinationPath}'.");
}
}
catch (FileNotFoundException)
{
Console.WriteLine($"Error: Source file '{sourcePath}' not found.");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred during async copy: {ex.Message}");
}
}
// How to call it
// static async Task Main(string[] args)
// {
// // Create a dummy large file for testing
// await File.WriteAllBytesAsync("LargeSourceFile.bin", new byte[10 * 1024 * 1024]); // 10MB file
// await CopyFileAsync("LargeSourceFile.bin", "LargeDestinationFile.bin");
// File.Delete("LargeSourceFile.bin"); // Clean up
// File.Delete("LargeDestinationFile.bin"); // Clean up
// }
نکته: در کانستراکتور FileStream
، آرگومان useAsync
را روی true
تنظیم کنید تا FileStream بهینه برای عملیات ناهمگام باز شود. همچنین، تعیین اندازه بافر (مثلاً 4096 بایت) میتواند عملکرد را بهبود بخشد.
استفاده از async
و await
برای عملیات I/O نه تنها بهینهسازی عملکردی را به همراه دارد، بلکه به شما کمک میکند تا برنامههایی بسازید که در مواجهه با عملیاتهای کند، همچنان پاسخگو و کاربرپسند باقی بمانند.
مدیریت خطا و بهترین شیوهها (Best Practices)
هنگام کار با فایلها و مسیرها، مواجهه با خطاها اجتنابناپذیر است. فایل ممکن است وجود نداشته باشد، دسترسی به آن ممنوع باشد، دیسک پر باشد یا عملیات دیگری در حال استفاده از فایل باشد. مدیریت صحیح خطاها و رعایت بهترین شیوهها در کدنویسی I/O، به شما کمک میکند تا برنامههایی پایدار، امن و کارآمد بسازید.
۱. استفاده از بلوک using و try-catch-finally
مهمترین جنبه در مدیریت فایلها، اطمینان از بسته شدن صحیح منابع فایل پس از اتمام کار است، حتی در صورت بروز خطا. عدم بسته شدن منابع میتواند منجر به قفل شدن فایل، مصرف منابع سیستم و نشت حافظه شود.
استفاده از using
Statement (توصیه شده)
کلاسهای جریان (Stream) در .NET (مانند FileStream
, StreamReader
, StreamWriter
, BinaryReader
, BinaryWriter
) رابط IDisposable
را پیادهسازی میکنند. بلوک using
به طور خودکار متد Dispose()
را در انتهای بلوک (یا هنگام خروج از آن، حتی در صورت بروز خطا) فراخوانی میکند و اطمینان میدهد که منابع فایل به درستی آزاد میشوند.
string filePath = "example.txt";
try
{
using (StreamReader reader = new StreamReader(filePath)) // Resource will be disposed automatically
{
string content = reader.ReadToEnd();
Console.WriteLine("Content: " + content);
} // reader.Dispose() is called here implicitly
}
catch (FileNotFoundException)
{
Console.WriteLine($"Error: File '{filePath}' not found.");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine($"Error: Access to '{filePath}' is denied. Check permissions.");
}
catch (IOException ex) // Catch other I/O related errors
{
Console.WriteLine($"An I/O error occurred: {ex.Message}");
}
catch (Exception ex) // Catch any other unexpected errors
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
میتوانید چندین using
را به صورت تو در تو یا به صورت chaining استفاده کنید:
string filePath = "data.bin";
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Open))
using (BinaryReader br = new BinaryReader(fs))
{
// Read binary data
int value = br.ReadInt32();
Console.WriteLine("Read value: " + value);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
استفاده از try-catch-finally
(در صورت عدم امکان استفاده از using
)
در سناریوهای نادر که using
Statement مناسب نیست (مثلاً وقتی یک Stream
باید به متدی دیگر پاس داده شود و مدیریت بسته شدن آن در جای دیگری است)، میتوانید از بلوک finally
برای اطمینان از بسته شدن دستی منابع استفاده کنید. این روش پیچیدهتر است و مستعد خطا است، بنابراین using
همیشه ترجیح داده میشود.
FileStream fs = null;
try
{
fs = new FileStream("manual_close.txt", FileMode.Open);
// Do something with fs
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
// Ensure the stream is closed, even if an error occurred
if (fs != null)
{
fs.Dispose(); // or fs.Close();
}
}
۲. نکات امنیتی و مجوزها
امنیت فایلها یک ملاحظه مهم است، به خصوص در برنامههایی که در محیطهای چند کاربره یا سرور اجرا میشوند. یک برنامه باید فقط به فایلها و دایرکتوریهایی دسترسی داشته باشد که برای عملکردش ضروری است.
- کمترین امتیاز (Least Privilege): همیشه سعی کنید با کمترین امتیازات ممکن به فایلها دسترسی پیدا کنید. مثلاً اگر فقط نیاز به خواندن دارید، فایل را با
FileAccess.Read
باز کنید. - اعتبارسنجی مسیرها: هرگز به ورودی کاربر برای مسیر فایلها بدون اعتبارسنجی اعتماد نکنید. کاربران مخرب ممکن است سعی کنند از طریق مسیرهای دستکاری شده (مانند
../../etc/passwd
) به فایلهای حساس دسترسی پیدا کنند.- از
Path.GetFullPath()
برای تبدیل مسیرهای نسبی به مطلق استفاده کنید. - بررسی کنید که مسیر نهایی در یک دایرکتوری امن و مورد انتظار قرار دارد.
Path.IsPathFullyQualified()
را برای بررسی اینکه آیا مسیر مطلق است استفاده کنید.
- از
- مجوزهای سیستم عامل: برنامه شما باید مجوزهای مناسب سیستم عامل برای خواندن/نوشتن در مکانهای مشخص را داشته باشد. اگر برنامه شما با خطای
UnauthorizedAccessException
مواجه شد، ممکن است نیاز باشد مجوزهای فولدر را بررسی کنید یا برنامه را با امتیازات بالاتری اجرا کنید (که این دومی معمولاً توصیه نمیشود). - اجتناب از مسیرهای ثابت در کد: از هاردکد کردن مسیرها به خصوص در محیطهای تولیدی خودداری کنید. به جای آن، از پیکربندیها (مانند
appsettings.json
)، متغیرهای محیطی یا مسیرهای خاص برنامه (مانندEnvironment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
) استفاده کنید.
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string myAppDir = Path.Combine(appDataPath, "MyApp");
string configFilePath = Path.Combine(myAppDir, "config.json");
Directory.CreateDirectory(myAppDir); // Ensure app-specific directory exists
// Now you can safely read/write configFilePath
۳. بهینهسازی عملکرد
عملکرد I/O میتواند گلوگاه یک برنامه باشد. برای بهینهسازی، نکات زیر را در نظر بگیرید:
- استفاده از بافرها: هنگام خواندن/نوشتن فایلهای بزرگ با
FileStream
، از بافرهای با اندازه مناسب (مثلاً 4KB یا 8KB) استفاده کنید.StreamReader
وStreamWriter
به طور داخلی از بافرها استفاده میکنند. - I/O ناهمگام (Async I/O): برای جلوگیری از بلوکه شدن رشتهها و بهبود پاسخگویی، به خصوص در برنامههای UI یا سرور، از متدهای
Async
استفاده کنید. - کاهش عملیات باز و بسته کردن فایل: باز کردن و بستن مکرر یک فایل باعث سربار عملکردی میشود. اگر نیاز دارید چندین بار به یک فایل دسترسی پیدا کنید، فایل را باز نگه دارید و پس از اتمام تمام عملیات آن را ببندید (با استفاده از
using
). - از
File.ReadAllText
/WriteAllText
با احتیاط استفاده کنید: این متدها برای فایلهای کوچک راحت هستند، اما برای فایلهای بزرگ میتوانند منجر به مصرف زیاد حافظه شوند. برای فایلهای بزرگ، ازStreamReader
/StreamWriter
استفاده کنید. - حافظه نهان (Caching): اگر به طور مکرر به محتوای یک فایل کوچک نیاز دارید، پس از اولین بار خواندن، آن را در حافظه نهان (memory cache) ذخیره کنید تا از خواندن مکرر از دیسک جلوگیری شود.
- فشردهسازی: برای ذخیره حجم زیادی از دادهها، از کتابخانههای فشردهسازی (مانند
System.IO.Compression
برای فایلهای GZip یا Zip) استفاده کنید تا فضای دیسک و زمان I/O کاهش یابد.
با رعایت این اصول و بهترین شیوهها، میتوانید اطمینان حاصل کنید که عملیات I/O فایل در برنامههای C# شما قابل اعتماد، امن و کارآمد خواهد بود.
نتیجهگیری
در این مقاله، به بررسی جامع و تخصصی مبحث کار با فایلها و مسیرها در C# پرداختیم. از مفاهیم بنیادی سیستم فایل و انواع مسیرها شروع کرده و با معرفی فضای نام System.IO
، کلاسهای کلیدی مانند File
، Directory
، Path
، FileStream
، StreamReader
، StreamWriter
، BinaryReader
و BinaryWriter
را تشریح کردیم. با ارائه مثالهای کاربردی، نشان دادیم که چگونه میتوان دادههای متنی و باینری را از فایلها خواند و در آنها نوشت.
ما همچنین به اهمیت مدیریت مسیرها با کلاس Path
برای ایجاد کدهای قابل حمل بین پلتفرمها و مدیریت دایرکتوریها با کلاس Directory
پرداختیم. یکی از مهمترین بخشهای این راهنما، تمرکز بر عملیات ورودی/خروجی ناهمگام (Asynchronous I/O) با استفاده از async
و await
بود که برای بهبود پاسخگویی و کارایی برنامهها در مواجهه با عملیاتهای کند I/O ضروری است.
در نهایت، به مدیریت خطا و بهترین شیوهها اشاره کردیم؛ از اهمیت استفاده از بلوک using
برای اطمینان از آزادی صحیح منابع گرفته تا نکات امنیتی مربوط به مجوزهای فایل و اعتبارسنجی مسیرها، و همچنین روشهای بهینهسازی عملکرد. درک و به کارگیری این اصول برای هر توسعهدهندهای که با C# و .NET کار میکند، حیاتی است، چرا که تعامل با سیستم فایل بخش جداییناپذیری از اکثر برنامههای کاربردی است.
با تسلط بر مفاهیم و تکنیکهای ارائه شده در این مقاله، شما اکنون قادر خواهید بود تا با اطمینان بیشتری به مدیریت فایلها و دایرکتوریها در پروژههای C# خود بپردازید. این دانش نه تنها به شما در پیادهسازی ویژگیهای پایه کمک میکند، بلکه راه را برای حل چالشهای پیچیدهتر در پردازش دادههای بزرگ و ایجاد برنامههای کاربردی پایدار و با عملکرد بالا هموار میسازد.
“تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT”
"تسلط به برنامهنویسی پایتون با هوش مصنوعی: آموزش کدنویسی هوشمند با ChatGPT"
"با شرکت در این دوره جامع و کاربردی، به راحتی مهارتهای برنامهنویسی پایتون را از سطح مبتدی تا پیشرفته با کمک هوش مصنوعی ChatGPT بیاموزید. این دوره، با بیش از 6 ساعت محتوای آموزشی، شما را قادر میسازد تا به سرعت الگوریتمهای پیچیده را درک کرده و اپلیکیشنهای هوشمند ایجاد کنید. مناسب برای تمامی سطوح با زیرنویس فارسی حرفهای و امکان دانلود و تماشای آنلاین."
ویژگیهای کلیدی:
بدون نیاز به تجربه قبلی برنامهنویسی
زیرنویس فارسی با ترجمه حرفهای
۳۰ ٪ تخفیف ویژه برای دانشجویان و دانش آموزان