آموزش سی‌شارپ (#C)؛ زبانی که دروازه ورود شما به دنیای دات‌نت است

سی‌شارپ زبان اصلی اکوسیستم دات‌نت است و بدون تسلط بر آن، هیچ آموزش ASP.NET برایتان معنا پیدا نمی‌کند. در این راهنما، مسیر یادگیری زبان را از پایه‌ترین مفاهیم تا موضوعاتی که در پروژه‌های واقعی هر روز به کار می‌آیند — مثل LINQ و برنامه‌نویسی ناهمگام — با مثال‌های عملی مرور می‌کنیم.

چرا سی‌شارپ؟

سی‌شارپ زبانی است شیءگرا، تایپ‌استاتیک و چندمنظوره که مایکروسافت آن را در ابتدای دهه ۲۰۰۰ معرفی کرد و از آن زمان تاکنون به‌طور پیوسته توسعه یافته است. امروز با یک زبان یاد گرفتن، می‌توانید وب‌سایت بسازید (ASP.NET Core)، اپلیکیشن موبایل بنویسید، برنامه دسکتاپ ویندوز تولید کنید، بازی بسازید (موتور Unity) و سرویس ابری پیاده کنید. این گستردگی، سرمایه‌گذاری روی یادگیری زبان را توجیه می‌کند.

اما مهم‌ترین دلیل توصیه سی‌شارپ برای شروع، چیز دیگری است: این زبان شما را وادار می‌کند درست فکر کنید. تایپ استاتیک یعنی کامپایلر پیش از اجرا بسیاری از اشتباهات شما را نشان می‌دهد. پیام‌های خطا دقیق و قابل فهم‌اند. ابزار توسعه هوشمند است و در حین تایپ راهنمایی می‌کند. برای کسی که تازه شروع کرده، این بازخورد سریع و صریح تفاوت میان یادگیری و سردرگمی است.

نکته سوم پایداری زبان است. کدی که ده سال پیش نوشته شده، امروز هم قابل کامپایل است. برخلاف برخی اکوسیستم‌ها که هر دو سال یک‌بار همه‌چیز را از نو می‌نویسند، دانشی که در سی‌شارپ کسب می‌کنید ماندگار است.

اولین برنامه و ساختار کد

یک برنامه کنسول بسازید و اجرا کنید:

dotnet new console -o HelloCSharp
cd HelloCSharp
dotnet run

فایل Program.cs در ساده‌ترین شکل خود چنین است:

Console.WriteLine("سلام دنیا!");

Console.Write("نامت چیست؟ ");
string? name = Console.ReadLine();

Console.WriteLine($"خوش آمدی {name}!");

توجه کنید که در نسخه‌های جدید زبان، لازم نیست کلاس و متد Main را دستی بنویسید؛ به این قابلیت «عبارات سطح بالا» می‌گویند. علامت $ پیش از رشته هم یعنی درون‌یابی رشته: می‌توانید متغیرها را مستقیماً داخل آکولاد قرار دهید، که خواناتر از چسباندن رشته‌هاست.

نوع‌های داده و متغیرها

سی‌شارپ نوع‌ها را به دو خانواده اصلی تقسیم می‌کند: نوع‌های مقداری که خودِ مقدار را نگه می‌دارند و نوع‌های ارجاعی که آدرس یک شیء در حافظه را نگه می‌دارند.

int     count    = 42;                 // عدد صحیح
long    bigNum   = 9_000_000_000L;     // عدد بزرگ
double  ratio    = 3.14;               // اعشاری معمولی
decimal price    = 1_250_000.75m;      // اعشاری دقیق، مناسب پول
bool    isActive = true;               // منطقی
char    letter   = 'A';                // یک نویسه
string  title    = "آموزش سی‌شارپ";     // رشته
DateTime now     = DateTime.Now;       // تاریخ و زمان

var    inferred  = 100;                // نوع از مقدار استنتاج می‌شود
const  double Pi = 3.14159;            // ثابت، غیرقابل تغییر
قانون طلایی مبالغ مالی: برای قیمت، حقوق و هر مقدار پولی همیشه از decimal استفاده کنید، نه double. نوع double برای محاسبات علمی طراحی شده و خطای گردکردن دارد؛ در محاسبات مالی همین خطای کوچک به مرور به اختلاف واقعی تبدیل می‌شود.

نوع‌های تهی‌پذیر

گاهی «مقدار نداشتن» خودش یک وضعیت معنادار است. علامت سؤال پس از نوع، اجازه می‌دهد متغیر مقدار null بگیرد. سی‌شارپ مدرن با ویژگی «مرجع‌های تهی‌پذیر» به شما هشدار می‌دهد که کجا ممکن است با null روبه‌رو شوید — قابلیتی که جلوی یکی از پرتکرارترین خطاهای زمان اجرا را می‌گیرد.

int? age = null;
string? nickname = null;

// عملگر ادغام تهی: اگر تهی بود، مقدار جایگزین
string display = nickname ?? "کاربر مهمان";

// دسترسی امن: اگر تهی بود، کل عبارت تهی می‌شود
int? length = nickname?.Length;

کنترل جریان و حلقه‌ها

int score = 85;

if (score >= 90)      Console.WriteLine("عالی");
else if (score >= 70) Console.WriteLine("خوب");
else                  Console.WriteLine("نیاز به تلاش بیشتر");

// تطبیق الگو در switch
string grade = score switch
{
    >= 90 => "الف",
    >= 80 => "ب",
    >= 70 => "ج",
    _     => "د"
};

for (int i = 1; i <= 5; i++)
    Console.WriteLine($"شماره {i}");

var cities = new[] { "تهران", "شیراز", "اصفهان" };
foreach (var city in cities)
    Console.WriteLine(city);

int n = 3;
while (n > 0) { Console.WriteLine(n); n--; }

عبارت switch در سبک جدید — که به آن «عبارت switch» می‌گویند — یک مقدار برمی‌گرداند و بسیار فشرده‌تر از شکل کلاسیک است. علامت زیرخط نقش حالت پیش‌فرض را دارد. این سبک نوشتن در کدهای حرفه‌ای دات‌نت امروز بسیار رایج است و خواندنش را باید بلد باشید.

متدها و پارامترها

// متد ساده با مقدار بازگشتی
int Add(int a, int b) => a + b;

// پارامتر اختیاری با مقدار پیش‌فرض
decimal ApplyDiscount(decimal price, decimal percent = 10)
    => price - (price * percent / 100);

// پارامتر با تعداد نامشخص
int Sum(params int[] numbers)
{
    int total = 0;
    foreach (var n in numbers) total += n;
    return total;
}

// آرگومان نام‌دار برای خوانایی بیشتر
var final = ApplyDiscount(price: 500_000, percent: 25);

توصیه حرفه‌ای: متدها را کوتاه و تک‌مسئولیتی نگه دارید. اگر نام گویایی برای یک متد پیدا نمی‌کنید، احتمالاً آن متد بیش از یک کار انجام می‌دهد و باید شکسته شود. این عادت ساده، بیش از هر تکنیک پیشرفته‌ای کیفیت کد شما را بالا می‌برد.

شیءگرایی؛ قلب زبان

برنامه‌نویسی شیءگرا یعنی مدل کردن دنیای مسئله با اشیائی که هم داده دارند و هم رفتار. چهار ستون اصلی آن را یک‌به‌یک ببینیم.

۱) کپسوله‌سازی

جزئیات داخلی پنهان بماند و دسترسی فقط از راه‌های کنترل‌شده انجام شود:

public class BankAccount
{
    private decimal _balance;                 // خصوصی: از بیرون دیده نمی‌شود

    public string Owner { get; }              // فقط‌خواندنی
    public decimal Balance => _balance;       // خاصیت محاسباتی

    public BankAccount(string owner, decimal initial)
    {
        if (initial < 0)
            throw new ArgumentException("موجودی اولیه نمی‌تواند منفی باشد");

        Owner = owner;
        _balance = initial;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0) throw new ArgumentException("مبلغ باید مثبت باشد");
        _balance += amount;
    }

    public bool Withdraw(decimal amount)
    {
        if (amount > _balance) return false;
        _balance -= amount;
        return true;
    }
}

دقت کنید که موجودی حساب هرگز مستقیماً قابل تغییر نیست؛ فقط از طریق دو متدی که قواعد کسب‌وکار را رعایت می‌کنند. این دقیقاً معنای کپسوله‌سازی است و اگر آن را جدی بگیرید، کلاس‌هایتان هرگز در وضعیت نامعتبر قرار نمی‌گیرند.

۲) وراثت

public class Person
{
    public string Name { get; set; } = "";
    public virtual string Describe() => $"شخصی به نام {Name}";
}

public class Student : Person
{
    public string University { get; set; } = "";
    public override string Describe()
        => $"دانشجوی {University} به نام {Name}";
}

۳) چندریختی

List<Person> people = new()
{
    new Person  { Name = "مریم" },
    new Student { Name = "رضا", University = "شریف" }
};

foreach (var p in people)
    Console.WriteLine(p.Describe());   // هرکس نسخه خودش را اجرا می‌کند

۴) انتزاع

فقط آنچه اهمیت دارد را در معرض دید بگذارید و پیچیدگی را پشت یک قرارداد ساده پنهان کنید. کسی که از کلاس شما استفاده می‌کند نباید بداند داده کجا ذخیره می‌شود یا محاسبه چگونه انجام می‌گیرد؛ فقط باید بداند چه ورودی بدهد و چه خروجی بگیرد. این همان چیزی است که در بخش بعد با اینترفیس‌ها عملی می‌شود و بدون آن، نوشتن کد قابل تست در ASP.NET Core تقریباً ناممکن است.

یک هشدار مهم درباره وراثت: تازه‌کارها معمولاً به آن بیش از حد علاقه پیدا می‌کنند و سلسله‌مراتب‌های عمیق می‌سازند که پس از مدتی هیچ‌کس نمی‌فهمد یک رفتار از کجا می‌آید. توصیه رایج و درست در جامعه دات‌نت این است که ترکیب را بر وراثت ترجیح دهید: به‌جای اینکه کلاس شما از کلاس دیگری ارث ببرد، آن را به‌عنوان یک وابستگی در خود نگه دارد. نتیجه، کدی انعطاف‌پذیرتر است که تغییر دادنش شکننده نیست.

اینترفیس و کلاس انتزاعی

اینترفیس یک قرارداد است: می‌گوید چه کارهایی باید انجام شود، اما نمی‌گوید چگونه. این مفهوم پایه‌ای‌ترین ابزار شما برای نوشتن کد قابل تست و قابل تعویض است و مستقیماً به تزریق وابستگی در ASP.NET Core وصل می‌شود.

public interface INotifier
{
    Task SendAsync(string to, string message);
}

public class SmsNotifier : INotifier
{
    public Task SendAsync(string to, string message)
    {
        Console.WriteLine($"پیامک به {to}: {message}");
        return Task.CompletedTask;
    }
}

public class EmailNotifier : INotifier
{
    public Task SendAsync(string to, string message)
    {
        Console.WriteLine($"ایمیل به {to}: {message}");
        return Task.CompletedTask;
    }
}

// کد مصرف‌کننده نمی‌داند کدام پیاده‌سازی را دریافت کرده است
public class OrderService
{
    private readonly INotifier _notifier;
    public OrderService(INotifier notifier) => _notifier = notifier;

    public async Task PlaceAsync(string customer)
    {
        // ... منطق ثبت سفارش
        await _notifier.SendAsync(customer, "سفارش شما ثبت شد.");
    }
}

تفاوت با کلاس انتزاعی: کلاس انتزاعی می‌تواند پیاده‌سازی مشترک و فیلد داشته باشد، اما هر کلاس فقط از یک کلاس می‌تواند ارث ببرد. اینترفیس پیاده‌سازی نگه نمی‌دارد، ولی یک کلاس می‌تواند ده‌ها اینترفیس را پیاده کند. قاعده عملی: اگر رابطه «هست یک» با کد مشترک دارید، کلاس انتزاعی؛ اگر صرفاً می‌خواهید قابلیتی را تضمین کنید، اینترفیس.

مجموعه‌ها و جنریک

// آرایه: اندازه ثابت
int[] numbers = { 5, 3, 9, 1 };

// لیست: اندازه پویا
List<string> cities = new() { "تهران", "مشهد" };
cities.Add("تبریز");
cities.Remove("مشهد");

// دیکشنری: کلید و مقدار
Dictionary<string, int> population = new()
{
    ["تهران"] = 9_000_000,
    ["شیراز"] = 1_500_000
};

if (population.TryGetValue("شیراز", out var pop))
    Console.WriteLine(pop);

// مجموعه یکتا
HashSet<string> tags = new() { "asp", "csharp", "asp" };  // فقط دو عضو

// صف و پشته
Queue<string> queue = new();
Stack<string> stack = new();

علامت‌های <> نشانه جنریک هستند: یعنی نوع داده به‌عنوان پارامتر داده می‌شود. مزیت آن این است که هم ایمنی نوع دارید (کامپایلر جلوی اشتباه را می‌گیرد) و هم لازم نیست برای هر نوع داده یک کلاس جداگانه بنویسید. خودتان هم می‌توانید کلاس و متد جنریک بسازید:

public class Result<T>
{
    public bool Success { get; init; }
    public T? Data { get; init; }
    public string? Error { get; init; }

    public static Result<T> Ok(T data) => new() { Success = true,  Data = data };
    public static Result<T> Fail(string error) => new() { Success = false, Error = error };
}

LINQ؛ زبان پرس‌وجو در دل زبان

LINQ به شما اجازه می‌دهد روی هر مجموعه‌ای — لیست درون‌حافظه، جدول پایگاه داده یا سند XML — با یک سینتکس واحد پرس‌وجو بزنید. یادگیری آن الزامی است، چون تمام کوئری‌های EF Core با همین ابزار نوشته می‌شوند.

var products = new List<Product>
{
    new("لپ‌تاپ",  45_000_000, "دیجیتال"),
    new("موبایل",  25_000_000, "دیجیتال"),
    new("صندلی",    8_000_000, "اداری"),
    new("میز",     12_000_000, "اداری")
};

// فیلتر، مرتب‌سازی و تصویر
var expensive = products
    .Where(p => p.Price > 10_000_000)
    .OrderByDescending(p => p.Price)
    .Select(p => new { p.Name, p.Price })
    .ToList();

// گروه‌بندی و تجمیع
var byCategory = products
    .GroupBy(p => p.Category)
    .Select(g => new
    {
        Category = g.Key,
        Count    = g.Count(),
        Total    = g.Sum(p => p.Price),
        Average  = g.Average(p => p.Price)
    });

// عملگرهای پرکاربرد
bool anyCheap   = products.Any(p => p.Price < 10_000_000);
bool allDigital = products.All(p => p.Category == "دیجیتال");
var  first      = products.FirstOrDefault(p => p.Category == "اداری");
var  cheapest   = products.MinBy(p => p.Price);
var  page2      = products.Skip(2).Take(2);
نکته مهم درباره اجرای معوق: بیشتر عملگرهای LINQ تا زمانی که نتیجه را واقعاً نخواهید اجرا نمی‌شوند. فراخوانی ToList()، Count() یا حلقه foreach است که کوئری را اجرا می‌کند. در EF Core این موضوع اهمیت دوچندان دارد: اگر زودتر از موعد ToList() بزنید، فیلترها به‌جای پایگاه داده در حافظه اجرا می‌شوند و کارایی به‌شدت افت می‌کند.

دو سبک نوشتن LINQ

آنچه در مثال بالا دیدید سبک متدی است که با نقطه و فراخوانی پیاپی متدها نوشته می‌شود و در پروژه‌های واقعی بیش از نود درصد کدها به همین شکل نوشته می‌شوند. سبک دوم، نحو پرس‌وجویی است که ظاهری شبیه SQL دارد و برای کوئری‌های پیچیده با چند اتصال و گروه‌بندی گاهی خواناتر است. هر دو در نهایت به یک چیز کامپایل می‌شوند، پس انتخاب میان آن‌ها صرفاً سلیقه و خوانایی است؛ اما در یک پروژه، یکدست ماندن سبک ارزش بیشتری از انتخاب «بهترین» سبک دارد.

نکته‌ای که ارزش LINQ را دوچندان می‌کند این است که همان مهارتی که برای کار با لیست‌های درون‌حافظه‌ای یاد می‌گیرید، بدون هیچ تغییری برای کوئری زدن به پایگاه داده هم به کار می‌آید. در EF Core همان Where و OrderBy و Select را می‌نویسید، اما این‌بار فریم‌ورک آن را به SQL ترجمه می‌کند و روی سرور پایگاه داده اجرا می‌کند. به همین دلیل، وقتی برای زبان LINQ وقت می‌گذارید، در واقع دارید روی مهم‌ترین مهارت روزمره خود در توسعه وب سرمایه‌گذاری می‌کنید.

مدیریت خطا

try
{
    var text = File.ReadAllText("data.txt");
    var value = int.Parse(text);
    Console.WriteLine(100 / value);
}
catch (FileNotFoundException ex)
{
    Console.WriteLine($"فایل پیدا نشد: {ex.FileName}");
}
catch (FormatException)
{
    Console.WriteLine("محتوای فایل عدد معتبر نیست.");
}
catch (DivideByZeroException)
{
    Console.WriteLine("تقسیم بر صفر مجاز نیست.");
}
finally
{
    Console.WriteLine("این بخش در هر حالت اجرا می‌شود.");
}

چهار قاعده حرفه‌ای در مدیریت خطا

  • استثنای عمومی نگیرید مگر در بالاترین لایه. گرفتن catch (Exception) در همه‌جا، خطاها را پنهان می‌کند و خطایابی را غیرممکن می‌سازد.
  • هرگز بلوک catch را خالی نگذارید. بلعیدن خطا بدترین کاری است که می‌توانید با آینده خودتان بکنید.
  • از استثنا برای جریان عادی برنامه استفاده نکنید. اگر ورودی کاربر ممکن است نامعتبر باشد، آن را اعتبارسنجی کنید، نه اینکه منتظر پرتاب استثنا بمانید.
  • برای بازپرتاب از throw; استفاده کنید، نه throw ex; — چون شکل دوم رد پشته اصلی را پاک می‌کند و منشأ خطا گم می‌شود.

برنامه‌نویسی ناهمگام با async و await

این بخش را با دقت بخوانید؛ چون هم مهم‌ترین مفهوم برای کار با ASP.NET است و هم بیشترین سوءتفاهم را دارد. وقتی برنامه شما با پایگاه داده یا شبکه کار می‌کند، بیشتر زمان صرف انتظار می‌شود، نه محاسبه. در حالت همگام، نخی که منتظر مانده هیچ کاری نمی‌کند اما اشغال است. برنامه‌نویسی ناهمگام همان نخ را در مدت انتظار آزاد می‌کند تا به درخواست‌های دیگر رسیدگی کند.

public async Task<string> FetchTitleAsync(string url)
{
    using var client = new HttpClient();

    string html = await client.GetStringAsync(url);   // نخ در این مدت آزاد است

    var start = html.IndexOf("<title>") + 7;
    var end   = html.IndexOf("</title>");

    return start > 6 && end > start
        ? html[start..end]
        : "بدون عنوان";
}

// اجرای موازی چند عملیات
public async Task RunParallelAsync()
{
    var t1 = FetchTitleAsync("https://example.com");
    var t2 = FetchTitleAsync("https://example.org");

    var results = await Task.WhenAll(t1, t2);   // هر دو همزمان اجرا می‌شوند

    foreach (var r in results) Console.WriteLine(r);
}

سه قانونی که نباید بشکنید

  1. هرگز .Result یا .Wait() صدا نزنید. این کار نخ فراخوان را قفل می‌کند و در برنامه‌های وب می‌تواند به بن‌بست کامل منجر شود. اگر متدی ناهمگام است، آن را با await فرا بخوانید.
  2. ناهمگامی را تا بالا ادامه دهید. اگر یک متد ناهمگام است، فراخوان آن هم باید ناهمگام باشد؛ نیمه‌کاره رها کردن این زنجیره، همه مزیت‌ها را از بین می‌برد.
  3. از async void پرهیز کنید، مگر در کنترل‌کننده رویداد. در این حالت استثناها قابل گرفتن نیستند و برنامه می‌تواند بی‌صدا از کار بیفتد.

یک نکته که تازه‌کارها را گیج می‌کند: ناهمگام بودن به معنای «سریع‌تر شدن» یک عملیات واحد نیست. اگر یک کوئری دو ثانیه طول می‌کشد، با async هم دو ثانیه طول می‌کشد. آنچه بهتر می‌شود، ظرفیت پاسخ‌گویی همزمان برنامه است: سروری که با کد همگام صد کاربر همزمان را جواب می‌داد، با کد ناهمگام می‌تواند چند برابر آن را مدیریت کند.

امکانات مدرن زبان که باید بشناسید

سی‌شارپ در نسخه‌های اخیر امکانات زیادی اضافه کرده که کد را کوتاه‌تر و ایمن‌تر می‌کنند. آشنایی با این‌ها لازم است چون در پروژه‌های امروزی همه‌جا دیده می‌شوند.

// رکورد: نوع داده تغییرناپذیر با مقایسه بر اساس مقدار
public record Product(string Name, decimal Price, string Category);

var p1 = new Product("موبایل", 25_000_000, "دیجیتال");
var p2 = p1 with { Price = 22_000_000 };   // کپی با تغییر جزئی

bool same = p1 == p2;                      // مقایسه بر اساس مقدار، نه ارجاع

// تطبیق الگو
string Classify(object item) => item switch
{
    Product { Price: > 20_000_000 } p => $"{p.Name}: گران",
    Product p                          => $"{p.Name}: اقتصادی",
    string s                           => $"متن: {s}",
    null                               => "مقدار تهی",
    _                                  => "ناشناخته"
};

// عملگر بازه و ایندکس از انتها
int[] arr = { 1, 2, 3, 4, 5 };
var middle = arr[1..4];   // 2،3،4
var last   = arr[^1];     // 5

رکوردها به‌ویژه برای DTOها در وب‌سرویس‌ها بسیار مناسب‌اند: کوتاه نوشته می‌شوند، تغییرناپذیرند و مقایسه‌شان بر اساس مقدار است — دقیقاً همان چیزی که برای انتقال داده لازم دارید.

نوع مقداری و ارجاعی؛ چیزی که پشت پرده اتفاق می‌افتد

بسیاری از باگ‌های عجیبی که تازه‌کارها با آن‌ها روبه‌رو می‌شوند، ریشه در نفهمیدن یک تفاوت ساده دارد: اینکه متغیر شما خودِ داده را نگه می‌دارد یا آدرس آن را. نوع‌های مقداری — مانند int، bool، decimal، DateTime و هر struct — مقدارشان مستقیماً در متغیر ذخیره می‌شود. وقتی چنین متغیری را به متغیر دیگری نسبت می‌دهید یا به متدی می‌فرستید، یک کپی کامل ساخته می‌شود و تغییر یکی هیچ اثری روی دیگری ندارد.

نوع‌های ارجاعی — یعنی هر کلاس، آرایه، رشته و اینترفیس — رفتار متفاوتی دارند. متغیر فقط یک اشاره‌گر به شیئی است که جای دیگری در حافظه ساخته شده. اگر همان شیء را به دو متغیر بدهید، هر دو به یک چیز اشاره می‌کنند و تغییر از یک مسیر، از مسیر دیگر هم دیده می‌شود. این دقیقاً همان جایی است که برنامه‌نویس تازه‌کار می‌گوید «من که این لیست را دست نزدم، چرا عوض شد؟»

رشته‌ها یک استثنای جالب‌اند: با اینکه نوع ارجاعی هستند، تغییرناپذیر محسوب می‌شوند. هر عملیاتی که به نظر می‌رسد یک رشته را عوض می‌کند، در واقع رشته تازه‌ای می‌سازد. به همین دلیل، چسباندن رشته داخل یک حلقه با هزاران تکرار بسیار پرهزینه است و در چنین مواردی باید از StringBuilder استفاده کنید. این یکی از پرتکرارترین نکاتی است که در بازبینی کد به تازه‌کارها گوشزد می‌شود.

درباره مدیریت حافظه هم خبر خوب این است که لازم نیست حافظه را دستی آزاد کنید؛ زباله‌روب دات‌نت این کار را خودکار انجام می‌دهد. اما یک مسئولیت همچنان با شماست: منابعی که به سیستم‌عامل وابسته‌اند — فایل باز، اتصال شبکه، اتصال پایگاه داده — باید صریحاً بسته شوند. برای همین از عبارت using استفاده می‌کنیم که تضمین می‌کند منبع حتی در صورت بروز خطا هم آزاد شود.

// منبع در پایان بلوک به‌طور خودکار آزاد می‌شود
using var reader = new StreamReader("data.txt");
string content = reader.ReadToEnd();

// ساخت کارآمد رشته در حلقه
var sb = new System.Text.StringBuilder();
for (int i = 0; i < 1000; i++)
    sb.AppendLine($"سطر شماره {i}");

string result = sb.ToString();

قراردادهای نام‌گذاری و کد تمیز

کدی که می‌نویسید یک‌بار نوشته می‌شود اما ده‌ها بار خوانده می‌شود — اغلب توسط خودتان، چند ماه بعد، وقتی جزئیات را فراموش کرده‌اید. جامعه دات‌نت قراردادهای روشنی دارد که رعایتشان باعث می‌شود کد شما برای هر برنامه‌نویس دیگری آشنا به نظر برسد.

  • نام کلاس‌ها، متدها و خاصیت‌ها با حرف بزرگ شروع می‌شود: ProductService، CalculateTotal.
  • پارامترها و متغیرهای محلی با حرف کوچک: totalPrice، userId.
  • فیلدهای خصوصی با زیرخط شروع می‌شوند: _repository.
  • نام اینترفیس‌ها با حرف I آغاز می‌شود: IOrderService.
  • متدهای ناهمگام به Async ختم می‌شوند: GetProductsAsync.
  • ثابت‌ها با حرف بزرگ و به شکل توصیفی نوشته می‌شوند: MaxRetryCount.

فراتر از نام‌گذاری، چند اصل ساده کیفیت کد شما را به‌طور محسوس بالا می‌برد. نام‌ها را توصیفی انتخاب کنید؛ daysUntilExpiry بی‌نهایت بهتر از d است و هیچ صرفه‌جویی‌ای در تایپ کردن، ارزش گم شدن معنا را ندارد. از اعداد جادویی پرهیز کنید و به‌جای نوشتن مستقیم عدد در وسط منطق، آن را در یک ثابت با نام گویا قرار دهید. هر متد باید یک کار انجام دهد و اگر برای توضیح کارش مجبورید از «و» استفاده کنید، احتمالاً باید شکسته شود.

درباره کامنت‌نویسی هم یک قاعده مهم وجود دارد: کامنت خوب چرایی را توضیح می‌دهد نه چگونگی را. اگر مجبورید توضیح دهید یک خط کد چه کاری می‌کند، معمولاً یعنی آن خط باید بازنویسی شود. اما اگر تصمیم غیرمعمولی گرفته‌اید — مثلاً به دلیل محدودیت یک سرویس بیرونی — نوشتن دلیل آن، هدیه‌ای به خودتان در آینده است.

تمرین‌های پیشنهادی

هیچ‌کس با خواندن، برنامه‌نویس نشده است. این پنج تمرین را به ترتیب انجام دهید؛ هرکدام یک دسته از مفاهیم بالا را در عمل جا می‌اندازد:

  1. ماشین‌حساب کنسولی: با منو، اعتبارسنجی ورودی و مدیریت خطای تقسیم بر صفر. تمرین کنترل جریان و try/catch.
  2. دفترچه تلفن: افزودن، جست‌وجو، ویرایش و حذف مخاطب با استفاده از Dictionary و ذخیره در فایل متنی.
  3. سیستم مدیریت کتابخانه: کلاس‌های کتاب، عضو و امانت با روابط بین آن‌ها. تمرین کامل شیءگرایی.
  4. تحلیلگر فروش: یک لیست از سفارش‌ها بسازید و با LINQ گزارش‌های گروه‌بندی‌شده، مجموع و میانگین تولید کنید.
  5. دانلودگر همزمان: محتوای چند آدرس اینترنتی را با Task.WhenAll همزمان بگیرید و زمان اجرا را با نسخه همگام مقایسه کنید.

وقتی این پنج تمرین را انجام دادید، پایه زبان را در حدی دارید که وارد فریم‌ورک شوید. قدم بعدی مشخص است: آموزش ASP.NET Core.

پرسش‌های متداول

سی‌شارپ برای شروع برنامه‌نویسی مناسب است؟

بله، یکی از بهترین گزینه‌هاست. سینتکس خوانا، پیام‌های خطای دقیق، ابزار توسعه قوی و مستندات کامل باعث می‌شود مسیر یادگیری هموارتر از بسیاری از زبان‌های دیگر باشد. ضمن اینکه بازار کار پایداری هم دارد.

تفاوت class و struct چیست؟

کلاس نوع ارجاعی است: متغیرها به یک شیء مشترک اشاره می‌کنند و تغییر از یک مسیر، از مسیر دیگر هم دیده می‌شود. ساختار نوع مقداری است و هنگام انتساب کپی می‌شود. برای اشیاء کوچک و تغییرناپذیر ساختار مناسب است، اما در بیشتر موارد کلاس انتخاب درست‌تری است.

چه زمانی باید async/await بنویسم؟

هر جا که عملیات ورودی و خروجی دارید: پایگاه داده، فایل، فراخوانی سرویس بیرونی. برای محاسبات صرفاً پردازشی که در حافظه انجام می‌شوند، ناهمگام کردن سودی ندارد و فقط پیچیدگی اضافه می‌کند.

یادگیری سی‌شارپ چقدر زمان می‌برد؟

برای رسیدن به سطحی که بتوانید وارد ASP.NET شوید، سه تا پنج هفته تمرین منظم کافی است. تسلط کامل بر موضوعات پیشرفته البته ماه‌ها طول می‌کشد، اما لازم نیست پیش از شروع فریم‌ورک منتظر آن بمانید.

جمع‌بندی

سی‌شارپ زبانی است که هرچه بیشتر با آن کار کنید، بیشتر قدر طراحی‌اش را می‌دانید. مسیر پیشنهادی ما ساده است: ابتدا نوع‌ها و کنترل جریان را تا حد روان شدن تمرین کنید، سپس شیءگرایی و اینترفیس‌ها را جدی بگیرید، بعد مجموعه‌ها و LINQ را به ابزار روزمره‌تان تبدیل کنید و در نهایت با async/await آشتی کنید. با این پایه، ورود به ASP.NET Core برایتان روان و لذت‌بخش خواهد بود و می‌توانید اولین پروژه‌تان را بسازید و طبق راهنمای انتشار و هاست روی اینترنت منتشر کنید.