
از ویژگی های سی شارپ Tuple ها هستند. اما Tuple ها دقیقاً چه کمکی به ما در سی شارپ می کنند؟
در اکثر مواقع زمانی که قصد داریم مدلی در سی شارپ ایجاد می کنیم (یا تعریف Class یا تعریف Struct). اما بعضی وقت ها ایجاد مدل های جدید کار زمانبری هست. اینجا Tuple ها به کمک ما می آیند.قبل از اینکه با نحوه کار Tuple ها آشنا بشیم یک مثال ساده می زنیم. فرض کنیم مدلی نیاز داریم که دو مقدار عددی و یک مقدار رشته تو خودش نگه داری کنه. قبلا به این صورت مدل مورد نیاز رو تعریف می کردیم:
public class Model { public Model(int value1, int value2, string value3) { this.Value1 = value1; this.Value2 = value2; this.Value3 = value3; } public int Value1 { get; set; } public int Value2 { get; set; } public string Value3 { get; set; } }
و از کد به این صورت استفاده می کردیم:
var model = new Model(1, 2, "A");
با معرفی Tuple ها رسماً بخش اول، یعنی معرفی مدل حذف شده و میتونیم به راحتی به صورت زیر مدل مورد نظر رو ایجاد کنیم:
var model = new Tuple<int, int, string>(1, 2, "A"); Console.WriteLine(model.Item1);
اما تعریف Tuple ها به صورت خیلی ساده تر هم امکان پذیر هست. برای تعریف Tuple بالا می تونیم از کد زیر استفاده کنیم:
var model = (1, 2, "A"); Console.WriteLine(model.Item1);
کد بالا یک مدل با سه مقدار که دو مقدار اولی Int و مقدار آخری String می باشد برای ایجاد می کند. حالا فرض کنید بخواییم نوع مقدار دوم از نوع decimal باشد، بوسیله کد زیر می تونیم نوع مقادیر رو مشخص کنیم:
(int, decimal, string) model = (1, 2, "A"); Console.WriteLine(model.Item1 + "," + model.Item2 + ", " + model.Item3);
یکی از مشکلاتی که در کد بالا می بینیم نام گذاری خصوصیت ها هستند که به ترتیب Item1 و Item2 و Item3 می باشند. برای تغییر این نام می توانیم به صورت زیر کد را تغییر بدهیم:
(int Number1, decimal Number2, string Name) model = (1, 2, "A"); Console.WriteLine(model.Number1 + "," + model.Number2 + ", " + model.Name);
یکی از استفاده های Tuple ها در مقادیر بازگشتی متدها هستند. برای مثال متدی رو در نظر بگیرید که نام، نام خانوادگی و سن رو برمیگردونه، بوسیله Tuple ها متد مورد نظر رو به صورت زیر تعریف می کنیم:
var emp = GetEmployee(); Console.WriteLine(emp.Firstname); static (string Firstname, string Lastname, int Age) GetEmployee() { return ("Hossein", "Ahmadi", 37); }
بلوکهای try-catch برای مدیریت خطاها (Exception Handling) در سیشارپ استفاده میشوند تا برنامه در صورت بروز خطا، به طور ناگهانی متوقف نشود
try { // کدی که ممکن است خطا تولید کند } catch (ExceptionType variableName) { // کدی که در صورت بروز خطا اجرا میشود }
مثال:
try { int a = 10; int b = 0; int result = a / b; // خطای تقسیم بر صفر } catch (DivideByZeroException ex) { Console.WriteLine("خطا: نمیتوان بر صفر تقسیم کرد!"); Console.WriteLine($"جزئیات: {ex.Message}"); }
همیشه اجرا میشود، چه خطا رخ دهد چه ندهد.
FileStream file = null; try { file = File.Open("test.txt", FileMode.Open); // کار با فایل } catch (FileNotFoundException ex) { Console.WriteLine("فایل پیدا نشد!"); } finally { // بستن فایل در هر صورت if (file != null) file.Close(); }
در برنامهنویسی با زبانهای مختلف، مدیریت منابع از اهمیت ویژهای برخوردار است. در سی شارپ نیز این موضوع بسیار حائز اهمیت است، چرا که برای بهینهسازی و آزاد کردن منابع سیستم، زبان برنامهنویسی سی شارپ ابزارهای قدرتمندی ارائه میدهد. یکی از این ابزارها واسط IDisposable است که به ما کمک میکند تا منابعی که خارج از حافظه اصلی قرار دارند را به درستی مدیریت و آزاد کنیم. در این مقاله به صورت جامع و کامل به بررسی IDisposable در سی شارپ میپردازیم و از مثالهای کاربردی استفاده میکنیم تا موضوع را به سادگی درک کنید.چرا IDisposable ضروری است؟
سی شارپ به عنوان یک زبان مدیریت شده شناخته میشود، به این معنا که حافظه به طور خودکار مدیریت میشود. اما همیشه فقط با حافظه اصلی سروکار نداریم. مواردی مانند فایلها، اتصال به دیتابیس، شبکهها، پایگاههای داده و دستگاههای ورودی و خروجی نیز از منابعی هستند که باید مدیریت شوند. مدیریت درست این منابع مهم است زیرا اگر منابعی مانند فایلها به موقع آزاد نشوند، باعث مشکلات عملکردی و حتی نشت منابع میشود.
using (ResourceType resource = new ResourceType()) { // کار با resource // به طور خودکار Dispose میشود }
مثال:
using (StreamWriter writer = new StreamWriter("file.txt")) { writer.WriteLine("Hello World"); } // به طور خودکار Dispose میشود
نسخه جدید سیشارپ (از نسخه 8 به بعد):
Using Declarations (بدون بلوک {}):
// قبل از سیشارپ 8 void OldMethod() { using (var reader = new StreamReader("file.txt")) { var content = reader.ReadToEnd(); // کارهای دیگر } // اینجا Dispose میشود } // از سیشارپ 8 به بعد void NewMethod() { using var reader = new StreamReader("file.txt"); var content = reader.ReadToEnd(); // کارهای دیگر } // اینجا Dispose میشود - در پایان scope
// بهترین روش برای مدیریت منابع using var resource1 = new Resource1(); using var resource2 = new Resource2(); // کار با منابع var result = resource1.Process(resource2.GetData()); // در پایان متد، به طور خودکار Dispose میشوند
چگونه از IDisposable استفاده کنیم؟
برای استفاده از IDisposable، کلاس شما باید این واسط را پیادهسازی کند و متد Dispose را ارائه دهد. این متد باید برای آزاد کردن منابعی که دیگر به آنها نیازی نیست، استفاده شود.
مثال:
Console.WriteLine("Start of method"); // این scope اصلی است using Flashlight flashlight1 = new Flashlight(); Console.WriteLine("Using flashlight..."); // کار با flashlight1 Console.WriteLine("End of method"); // flashlight1 به طور خودکار اینجا Dispose میشود public class Flashlight : IDisposable { public void Dispose() { Console.WriteLine("Flashlight disposed"); GC.SuppressFinalize(this); } }
از static زمانی استفاده کنید که عضو به حالت هیچ نمونهای از کلاس وابسته نباشد و بتواند مستقل از نمونهسازی عمل کند.
1. کلاس استاتیک (Static Class):
زمانی که:
کلاس فقط اعضای استاتیک دارد
نیازی به نمونهسازی (Instantiation) ندارید
برای گروهبندی توابع مرتبط بدون نیاز به حالت
// ✅ مناسب برای static class: public static class FileUtilities { public static string ReadAllText(string path) => File.ReadAllText(path); public static void WriteAllText(string path, string content) => File.WriteAllText(path, content); } // ❌ نامناسب برای static class: public class UserService // نیاز به حالت و وابستگیها دارد { private readonly IUserRepository _repository; public UserService(IUserRepository repository) { _repository = repository; } }
2. متد استاتیک (Static Method)
زمانی که:
متد فقط با پارامترهای ورودی کار میکند
به هیچ فیلد/خصوصیت نمونه (instance field/property) دسترسی ندارد
عملکردی خالص (Pure) و بدون عوارض جانبی دارد
public class Calculator { // ✅ مناسب برای static: public static int Add(int a, int b) => a + b; // فقط با پارامترها کار میکند public static bool IsValidEmail(string email) => Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"); // ❌ نامناسب برای static: private double _lastResult; public double AddAndStore(double a, double b) // به حالت نمونه وابسته است { _lastResult = a + b; return _lastResult; } }
3. متغیر استاتیک (Static Field/Property)
زمانی که:
داده باید بین همه نمونههای کلاس مشترک باشد
نیاز به نگهداری حالت در سطح کلاس دارید
برای کش (Cache)، تنظیمات، یا اشتراک منابع
public class Logger { // ✅ مناسب برای static: private static int _totalLogEntries = 0; // شمارنده مشترک private static readonly string LogPath = "app.log"; // تنظیمات مشترک // ❌ نامناسب برای static: private string _userSpecificLogPath; // برای هر کاربر متفاوت است }
حلقه foreach:
foreach یک حلقه تکرارکننده (iterator) است که برای پیمایش مجموعهها (Collections) و آرایهها استفاده میشود. برخلاف for، نیاز به اندیسدهی دستی ندارد.
foreach (type variable in collection) { // کدی که برای هر عنصر اجرا میشود }
مثال ۱: پیمایش آرایه
string[] fruits = { "سیب", "موز", "پرتقال", "انگور" }; foreach (string fruit in fruits) { Console.WriteLine(fruit); }
خروجی:
سیب موز پرتقال انگور
مثال ۲: پیمایش لیست
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; int sum = 0; foreach (int number in numbers) { sum += number; Console.WriteLine($"عدد: {number}, مجموع تاکنون: {sum}"); }
نکات مهم و محدودیتها:
۱. فقط خواندنی (Read-only)
List<int> numbers = new List<int> { 1, 2, 3 }; foreach (int num in numbers) { // num = num * 2; // ❌ خطا! نمیتوان مقدار را تغییر داد Console.WriteLine(num); } // راه حل: استفاده از for for (int i = 0; i < numbers.Count; i++) { numbers[i] = numbers[i] * 2; // ✅ قابل تغییر }
۲. نمیتوان در حین پیمایش، آیتم اضافه/حذف کرد
List<string> items = new List<string> { "A", "B", "C" }; foreach (var item in items) { if (item == "B") { // items.Remove(item); // ❌ خطای زمان اجرا: InvalidOperationException } } // راه حل: استفاده از for معکوس یا ToList() for (int i = items.Count - 1; i >= 0; i--) { if (items[i] == "B") { items.RemoveAt(i); // ✅ } }
۳. متغیر حلقه scope محلی دارد
foreach (int num in new int[] { 1, 2, 3 }) { int square = num * num; // فقط در این scope قابل دسترسی است Console.WriteLine(square); } // Console.WriteLine(square); // ❌ خطا: square در اینجا تعریف نشده
از سیشارپ 12 به بعد میتوان یک لیست پارامتر را مستقیم پس از اعلان کلاس (یا ساختار) رار دهید.
var temp = new Camid("Amid","GM"); temp.Print(); Console.WriteLine("Done!"); public class Camid (string FName, string LName) { ~Camid() { Console.WriteLine("Hello from ~Camid"); } public void Print() { Console.WriteLine($"{FName} {LName}"); } }
متد های partial:
متدهای partial به شما اجازه میدهند که یک متد را در یک کلاس تعریف کنید و در جای دیگر پیادهسازی کنید. این ویژگی مخصوصاً برای code generation و طراحی مبتنی بر قالب مفید است.
نکته مهم:
متدهای partial فقط در کلاسهای partial کار میکنند!
ساختار پایه:
public partial class MyClass { // ۱. تعریف متد (بدون بدنه) partial void MyPartialMethod(string message); // ۲. متد عادی که partial method را فراخوانی میکند public void PublicMethod() { Console.WriteLine("Public method called"); MyPartialMethod("Hello from public method"); } } public partial class MyClass { // ۳. پیادهسازی متد partial partial void MyPartialMethod(string message) { Console.WriteLine($"Partial method executed: {message}"); } }
محدودیتها:
فقط در کلاسهای partial کار میکنند
نمیتوانند access modifier داشته باشند (همیشه private هستند)
نمیتوانند virtual, abstract, override, sealed, extern, یا new باشند
فقط میتوانند void برگردانند
out پارامترها میتوانند داشته باشند، اما ref نمیتوانند
کاربردهای اصلی:
کدهای تولید شده خودکار (مثل Windows Forms, WPF, Entity Framework)
الگوهای طراحی hook/event
اضافه کردن منطق سفارشی بدون تغییر کدهای generator
ایجاد نقطههای گسترش (extension points) در کلاسها
مثال:
Console.WriteLine("=== ایجاد کاربر جدید ==="); var user = new User("Amid", 25); user.PrintInfo(); Console.WriteLine("\n=== تغییر نام کاربری ==="); user.Username = "Ali"; // partial methods فراخوانی میشوند Console.WriteLine("\n=== تغییر نام کاربری به مقدار نامعتبر ==="); user.Username = "Ab"; // partial method هشدار میدهد Console.WriteLine("\n=== تغییر سن ==="); user.Age = 30; Console.WriteLine("\n=== تغییر سن به مقدار نامعتبر ==="); user.Age = -5; // partial method خطا میدهد Console.WriteLine("\n=== اطلاعات نهایی ==="); user.PrintInfo(); Console.WriteLine($"آیا بزرگسال است؟ {user.IsAdult()}"); public partial class User { private string _username; private int _age; // تعریف partial method ها (فقط امضا - بدون بدنه) partial void OnUsernameChanging(string newUsername); partial void OnUsernameChanged(string oldUsername); partial void OnAgeChanging(int newAge); public string Username { get => _username; set { // فراخوانی partial method قبل از تغییر OnUsernameChanging(value); string oldUsername = _username; _username = value; // فراخوانی partial method بعد از تغییر OnUsernameChanged(oldUsername); } } public int Age { get => _age; set { OnAgeChanging(value); _age = value; } } public User(string username, int age) { _username = username; _age = age; } public void PrintInfo() { Console.WriteLine($"Username: {_username}, Age: {_age}"); } } // بخش دوم کلاس partial (همان فایل یا فایل دیگر) public partial class User { // پیادهسازی partial method ها (اختیاری) partial void OnUsernameChanging(string newUsername) { Console.WriteLine($"🔄 در حال تغییر نام کاربری به: {newUsername}"); if (newUsername.Length < 3) { Console.WriteLine("⚠️ نام کاربری خیلی کوتاه است!"); } } partial void OnUsernameChanged(string oldUsername) { Console.WriteLine($"✅ نام کاربری تغییر کرد: {oldUsername} → {_username}"); } partial void OnAgeChanging(int newAge) { if (newAge < 0) { Console.WriteLine("❌ سن نمیتواند منفی باشد!"); } else if (newAge > 150) { Console.WriteLine("❌ سن غیرواقعی است!"); } else { Console.WriteLine($"🔄 در حال تغییر سن به: {newAge}"); } } // اضافه کردن یک متد جدید به کلاس public bool IsAdult() { return _age >= 18; } }
کلاس sealec:
کلاس sealed (مهرومومشده) کلاسی است که نمیتوان از آن ارثبری کرد. مثل درختی که میگوییم "از این شاخه بالاتر نرو!"
کاربرد اصلی:
جلوگیری از ارثبری - وقتی میخواهید مطمئن شوید هیچکس نمیتواند از کلاس شما ارثبری کند.
ساختار پایه:
public sealed class MyClass { // اعضای کلاس }
این کار نمیتواند انجام شود:
public sealed class Animal { public string Name { get; set; } public void Eat() { Console.WriteLine($"{Name} is eating..."); } } // ❌ خطا: نمیتوان از کلاس sealed ارثبری کرد public class Dog : Animal // Error: 'Dog': cannot derive from sealed type 'Animal' { public void Bark() { Console.WriteLine("Woof!"); } }
اما این کار میشود:
// کلاس پایه (غیر sealed) public class Animal { public string Name { get; set; } public void Eat() { Console.WriteLine($"{Name} is eating..."); } } // کلاس sealed public sealed class Dog : Animal // ✅ میتواند از Animal ارثبری کند { public void Bark() { Console.WriteLine("Woof!"); } } // ❌ اما نمیتوان از Dog ارثبری کرد public class Puppy : Dog // Error: 'Puppy': cannot derive from sealed type 'Dog' { }
Telegram: @CaKeegan
Gmail : amidgm2020@gmail.com