MAXTO / C# FIELD NOTES · 2026

متدهای جدید C#؛ راهنمای عملی C# 14 با کد و خروجی

از یک متد توسعه‌ای خوانا تا ورودی‌های منعطف‌تر: امکانات تازهٔ سی‌شارپ را با هفت مثال کوچک یاد بگیرید و بدانید کدام قابلیت واقعاً به کار پروژهٔ شما می‌آید.

نویسنده: علی خان باباییبازبینی منابع: ۱۷ اکتبر ۲۰۲۶سطح: مقدماتی رو به متوسط
شروع با اولین نمونه‌کد ←

جدیدترین نسخهٔ C# چیست و «متد جدید» دقیقاً یعنی چه؟

در زمان بازبینی این نوشته، C# 14 نسخهٔ پایدار زبان روی .NET 10 است و C# 15 در شاخهٔ پیش‌نمایش .NET 11 قرار دارد. بنابراین کدهای اصلی این مقاله برای C# 14 نوشته شده‌اند. اگر بعداً این مطلب را می‌خوانید، پیش از انتخاب ابزار، وضعیت نسخه‌ها را در منابع انتهای مقاله بررسی کنید.

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

تاریخ قابلیت‌ها را درست بخوانید.

بلوک extension در C# 14 تازه است؛ اما خود extension method قدیمی‌تر است. params برای مجموعه‌های بیشتر از C# 13 و collection expression از C# 12 آمده‌اند. حضورشان در این راهنمای کاربردی به معنای معرفی در C# 14 نیست.

مستند رسمی: وضعیت و امکانات C# 14

جدول انتخاب: برای هر مسئله کدام قابلیت مناسب است؟

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

مقایسهٔ قابلیت‌های کاربردی C# برای متدها و اعضای مرتبط
نیازقابلیتاز نسخهمرز استفاده
رفتار خوانا روی نوع موجودextension membersC# 14نوع اصلی را تغییر نمی‌دهد.
کنترل مقدار هنگام تنظیم پراپرتیfieldC# 14برای پراپرتی است، نه هر متدی.
انتساب فقط وقتی شیء موجود استnull-conditional assignmentC# 14جای اعتبارسنجی اجباری را نمی‌گیرد.
لامبدای out با نوع قابل استنتاجپارامتر اصلاح‌شدهٔ بدون نوع صریحC# 14نوع delegate باید روشن باشد.
ورودی متغیر بدون محدودشدن به آرایهparams collectionsC# 13کارایی را باید اندازه گرفت.
نوشتن کوتاه مجموعهٔ ورودیcollection expressionsC# 12به نوع مقصد نیاز دارد.
نام نوع جنریک بدون آرگومانnameof نوع بازC# 14نام کامل یا شناسهٔ یکتا نیست.

جدول راهنمای انتخاب است؛ توضیح و لینک مرجع هر قابلیت در بخش مربوط آمده است.

۱. متدهای توسعه‌ای در C# 14؛ رفتار تازه برای یک نوع موجود

فرض کنید در چند بخش برنامه عنوان موضوع را تمیز و یکدست نمایش می‌دهید. تکرار چند دستور مشابه در همهٔ فراخوان‌ها، تغییر بعدی را سخت می‌کند. یک متد با نامی روشن می‌تواند این تصمیم را متمرکز کند. در نمونه، AsLabel() یک رشتهٔ نمایشی می‌سازد و IsBlank یک پرسش ساده دربارهٔ همان رشته است.

C# 14 · Program.csنمونهٔ مستقل و قابل کپی
using System;

string topic = "  csharp  ";
Console.WriteLine(topic.AsLabel());
Console.WriteLine(topic.IsBlank);

public static class TopicExtensions
{
    extension(string text)
    {
        public string AsLabel() => text.Trim().ToUpperInvariant();
        public bool IsBlank => string.IsNullOrWhiteSpace(text);
    }
}
دیدن خروجی نمونه

خروجی اجرای این کد با .NET SDK 10.0.101؛ این بخش کامپایلر آنلاین نیست.

CSHARP
False

متد و پراپرتی داخل بلوک extension(string text) گیرندهٔ مشترک دارند. پراپرتی با پرانتز فراخوانی نمی‌شود. بلوک در یک کلاس static سطح بالا و غیرجنریک قرار می‌گیرد؛ برای یک مثال کوتاه، همه چیز در همین فایل است.

چه زمانی extension انتخاب خوبی است؟

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

این روش عضو واقعی تازه‌ای به تعریف کلاس اصلی اضافه نمی‌کند و به اعضای خصوصی آن دسترسی ویژه نمی‌دهد. متدهای توسعه‌ای با نحو قدیمی this هم همچنان کاربرد دارند؛ برای مهاجرت عجله نکنید. تبدیل عنوان به حروف بزرگ در این نمونه یک قانون نمایشی است و نسخهٔ عمومیِ نرمال‌سازی شناسه، نام فارسی یا ورودی امنیتی محسوب نمی‌شود.

مستند رسمی: تعریف extension members و تفاوت آن با عضو اصلی

۲. field؛ کم‌کردن کد تکراری در پراپرتی‌ها

متدها و پراپرتی‌ها یکسان نیستند، اما طراحی آن‌ها به هم مربوط است. پراپرتی زیر اجازه نمی‌دهد عنوان خالی در شیء بماند و فاصلهٔ اضافی ابتدا و انتها را هم حذف می‌کند. کلیدواژهٔ field به فضای ذخیره‌سازی ساخته‌شده توسط کامپایلر اشاره دارد؛ برای این نیاز کوچک، متغیر خصوصی جداگانه‌ای نمی‌نویسیم.

C# 14 · Program.csنمونهٔ مستقل و قابل کپی
using System;

var article = new Article { Title = "  Modern C#  " };
Console.WriteLine(article.Title);

try
{
    article.Title = " ";
}
catch (ArgumentException)
{
    Console.WriteLine("Title is required.");
}

public sealed class Article
{
    public string Title
    {
        get;
        set => field = !string.IsNullOrWhiteSpace(value)
            ? value.Trim()
            : throw new ArgumentException("Title is required.");
    } = "Untitled";
}
دیدن خروجی نمونه

خروجی اجرای این کد با .NET SDK 10.0.101؛ این بخش کامپایلر آنلاین نیست.

Modern C#
Title is required.

مقدار اولیهٔ Untitled باعث می‌شود نمونه پیش از اولین تنظیم هم یک عنوان داشته باشد. ورودی دوم عمداً نامعتبر است؛ استثنا گرفته می‌شود تا خروجی قابل پیش‌بینی باقی بماند. در برنامهٔ واقعی، پیش از ذخیرهٔ داده علاوه بر این قاعده، محدودیت طول، سطح دسترسی و خطاهای قابل‌نمایش به کاربر را نیز جدا طراحی کنید.

اگر در کلاس قبلی عضوی با نام field دارید، هنگام مهاجرت به ابهام نام توجه کنید. همچنین وجود یک setter کوتاه به معنای اعتبارسنجی کامل دامنه نیست؛ اگر تغییر عنوان به چند تصمیم تجاری یا عملیات بیرونی وابسته است، یک متد مستقل می‌تواند منظور را بهتر بیان کند.

مستند رسمی: کلیدواژهٔ field در accessor پراپرتی

۳. انتساب شرطی با ?.؛ تفاوت «نبودن» با «خطا»

در C# 14 می‌توان از دسترسی شرطی در سمت چپ انتساب استفاده کرد. نمونهٔ زیر شمارنده دارد تا یک نکتهٔ مهم دیده شود: وقتی گیرنده null است، فراخوانی MakeTitle() در سمت راست هم انجام نمی‌شود.

C# 14 · Program.csنمونهٔ مستقل و قابل کپی
using System;

int calls = 0;
Article? article = null;
article?.Title = MakeTitle();
Console.WriteLine($"Calls: {calls}");

article = new Article();
article?.Title = MakeTitle();
Console.WriteLine($"Calls: {calls}; Title: {article!.Title}");

string MakeTitle()
{
    calls++;
    return "C# 14";
}

public sealed class Article
{
    public string Title { get; set; } = "";
}
دیدن خروجی نمونه

خروجی اجرای این کد با .NET SDK 10.0.101؛ این بخش کامپایلر آنلاین نیست.

Calls: 0
Calls: 1; Title: C# 14

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

اگر وجود شیء شرط درستی عملیات است، ردکردن بی‌صدای انتساب ممکن است عیب را مخفی کند. آنجا ورودی را بررسی کنید و خطای روشن بدهید. علامت ! در خط نمایش خروجی فقط هشدار تحلیل null را خاموش می‌کند؛ بررسی زمان اجرا یا محافظ امنیتی نیست. از ?. انتظار پشتیبانی از هر عملگر را هم نداشته باشید؛ ++ و -- در این شکل مجاز نیستند.

مستند رسمی: دسترسی شرطی و انتساب شرطی

۴. لامبدای out؛ کوتاه‌تر، با قرارداد روشن

وقتی شکل یک عملیات کوچک را به delegate می‌سپاریم، نوشتن نوع تک‌تک پارامترها همیشه لازم نیست. در C# 14 وجود اصلاح‌کننده‌ای مانند out لزوماً شما را مجبور به نوشتن نوع صریح همهٔ پارامترهای لامبدا نمی‌کند؛ کامپایلر در این مثال قرارداد را از NumberParser می‌گیرد.

C# 14 · Program.csنمونهٔ مستقل و قابل کپی
using System;

NumberParser parse = (text, out value) => int.TryParse(text, out value);

Console.WriteLine(parse("42", out var first));
Console.WriteLine(first);
Console.WriteLine(parse("invalid", out var second));
Console.WriteLine(second);

public delegate bool NumberParser(string text, out int value);
دیدن خروجی نمونه

خروجی اجرای این کد با .NET SDK 10.0.101؛ این بخش کامپایلر آنلاین نیست.

True
42
False
0

برای ورودی نامعتبر، TryParse مقدار false برمی‌گرداند و مقدار خروجی عددی صفر است. اگر فقط عدد را بردارید و نتیجهٔ بولی را نادیده بگیرید، صفرِ معتبر با شکست تبدیل اشتباه می‌شود. این همان جایی است که کوتاه‌شدن نحو نباید قرارداد رفتاری را پنهان کند.

لامبدا خودکار معادل یک عبارت قابل‌ترجمه برای هر ارائه‌دهندهٔ LINQ نیست. نمونهٔ حاضر delegate عادی است. هنگام کار با queryهای پایگاه داده، قابلیت ترجمهٔ عبارت توسط همان ارائه‌دهنده را بررسی کنید؛ هر کد C# قابل‌اجرا الزاماً به SQL ترجمه نمی‌شود.

مستند رسمی: قواعد پارامترها و نوع مقصد در lambda

۵. params ReadOnlySpan؛ ورودی متغیر با طراحی دقیق‌تر

از C# 13، params فقط به آرایه محدود نیست. می‌توان برای ورودی‌هایی مانند چند مقدار عددی، یک ReadOnlySpan<int> پذیرفت. فراخوان این مثال ساده می‌ماند و بدنهٔ متد هم قراردادی فقط‌خواندنی برای دنبالهٔ ورودی دارد.

C# 13+ · Program.csنمونهٔ مستقل و قابل کپی
using System;

Console.WriteLine(Total(5, 10, 15));
Console.WriteLine(Total());

static int Total(params ReadOnlySpan<int> values)
{
    int result = 0;
    foreach (int value in values)
        result = checked(result + value);
    return result;
}
دیدن خروجی نمونه

خروجی اجرای این کد با .NET SDK 10.0.101؛ این بخش کامپایلر آنلاین نیست.

30
0

فراخوانی بدون آرگومان، دنبالهٔ خالی می‌سازد و نتیجهٔ این تابع جمع صفر می‌شود. عبارت checked باعث می‌شود سرریز جمع به جای چرخیدن بی‌صدای مقدار، خطا بدهد. برای مبلغ واقعی، نوع داده و قواعد گردکردن را مستقل طراحی کنید؛ این نمونه یک جمع آموزشی روی int است.

وجود Span تضمین «بدون تخصیص حافظه» یا «سریع‌ترشدن قطعی» برای کل برنامه نیست. نوع فراخوان، طول داده، تبدیل‌های میانی و محیط اجرا اثر دارند. اگر متد در مسیر حساس کارایی است، همان سناریوی واقعی را اندازه بگیرید. در API عمومی نیز overload تازه ممکن است انتخاب فراخوان را تغییر دهد؛ قبل از افزودن شکل مشابه، سازگاری مصرف‌کننده‌ها را بررسی کنید.

مستند رسمی: params و انواع مجموعهٔ قابل پذیرش

۶. collection expression؛ ورودی خوانا برای متدها

نمادهای [] برای نوشتن مجموعه و .. برای گسترش عناصر، از C# 12 در اختیار ما هستند. در نمونه، آرایهٔ دوم عناصر آرایهٔ اول را می‌گیرد و عدد تازه را به انتها اضافه می‌کند. سپس دو متد عادی روی داده کار می‌کنند.

C# 12+ · Program.csنمونهٔ مستقل و قابل کپی
using System;
using System.Linq;

int[] initial = [10, 20];
int[] scores = [.. initial, 30];
Console.WriteLine(string.Join(", ", scores));
Console.WriteLine($"Sum: {scores.Sum()}");
دیدن خروجی نمونه

خروجی اجرای این کد با .NET SDK 10.0.101؛ این بخش کامپایلر آنلاین نیست.

10, 20, 30
Sum: 60

در این کد، Sum() متد LINQ است و با C# 14 معرفی نشده است. قابلیت مورد بحث، ساخت مجموعه با نحو کوتاه‌تر است. نوع مقصد را عمداً int[] نوشته‌ایم؛ نوشتن var scores = [10, 20]; بدون یک نوع مقصد مناسب، همان معنی را ندارد و کامپایل نمی‌شود.

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

مستند رسمی: collection expressions و وابستگی به نوع مقصد

۷. nameof برای نوع جنریک باز؛ نام‌گذاری بدون رشتهٔ دستی

در C# 14 می‌توان نام یک نوع جنریک باز را با nameof گرفت. این قابلیت در پیام‌های تشخیصی و بعضی ابزارهای توسعه کاربرد دارد؛ برای به‌دست‌آوردن همین نام ساده لازم نیست آرگومان نوع ساختگی انتخاب کنید.

C# 14 · Program.csنمونهٔ مستقل و قابل کپی
using System;
using System.Collections.Generic;

Console.WriteLine(nameof(Dictionary<,>));
Console.WriteLine(nameof(List<>));
دیدن خروجی نمونه

خروجی اجرای این کد با .NET SDK 10.0.101؛ این بخش کامپایلر آنلاین نیست.

Dictionary
List

خروجی شامل namespace، آرگومان‌های جنریک یا یک شناسهٔ یکتای پایدار نیست. دو نوع مختلف می‌توانند نام سادهٔ یکسانی داشته باشند. بنابراین از این مقدار به‌عنوان کلید ذخیره‌سازی، قرارداد API یا روش تشخیص نوع در امنیت استفاده نکنید. همین محدودیت کمک می‌کند قابلیت کوچک را در مسئلهٔ مناسب به کار ببریم.

مستند رسمی: nameof و نوع‌های جنریک باز

چطور کدها را روی دستگاه خودتان اجرا کنید؟

هر کادر یک نمونهٔ مستقل است. برای بازتولید خروجی‌ها، .NET 10 SDK داشته باشید، یک پروژهٔ کنسولی بسازید و محتوای Program.cs را با همان یک نمونه جایگزین کنید. چند نمونه را پشت سر هم در یک فایل نچسبانید؛ نام کلاس‌ها و دستورهای سطح بالا ممکن است با هم تداخل پیدا کنند.

dotnet --list-sdks
dotnet new console -n ModernCSharp -f net10.0
cd ModernCSharp
# Replace Program.cs with one example, then:
dotnet run

خروجی‌های نمایش‌داده‌شده برای ورودی‌های ثابت همین مقاله ثبت شده‌اند؛ دکمهٔ «دیدن خروجی» همان نتیجه را نشان می‌دهد و کد ویرایش‌شدهٔ شما را در مرورگر اجرا نمی‌کند. برای خروجی جدید، کد را در SDK اجرا کنید. دکمهٔ انتخاب/کپی متن، در صورت محدودیت مرورگر امکان انتخاب دستی و Ctrl+C را حفظ می‌کند.

نسخهٔ SDK با شمارهٔ نسخهٔ زبان یکی نیست. در یک پروژهٔ موجود، TargetFramework، SDK انتخاب‌شده در global.json و تنظیم احتمالی LangVersion را با هم بررسی کنید. برای یادگیری قابلیت پایدار، تنظیم تصادفی preview راه‌حل مناسبی برای خطای پروژه نیست.

مستند رسمی: انتخاب نسخهٔ زبان و ارتباط آن با SDK

C# 15 چه چیزی اضافه می‌کند؟ وضعیت پیش‌نمایش را بشناسید

در مستندات پیش‌نمایش فعلی، موضوع‌هایی مانند extension indexer، union type، سلسله‌مراتب بسته و امکانات تازهٔ collection expression مطرح شده‌اند. این بخش برای شناخت مسیر زبان است؛ مثال‌های قابل کپی این مقاله به این امکانات متکی نیستند.

کد یک پیش‌نمایش را در شاخه و محیط جداگانه بررسی کنید و نسخهٔ دقیق SDK را ثبت کنید. نحو یا محدودیت پیاده‌سازی ممکن است در پیش‌نمایش‌های بعدی تغییر کند. تصمیم ارتقای یک سرویس واقعی علاوه بر قابلیت زبان، به پشتیبانی ابزارها، وابستگی‌ها و سیاست نگهداری پروژه وابسته است.

مستند رسمی: وضعیت رسمی پیش‌نمایش C# 15

پیش از استفاده در پروژه: پنج تصمیم کاربردی

  1. یک نیاز مشخص انتخاب کنید. مثلاً حذف تکرار تبدیل عنوان؛ سپس قابلیت متناسب را به کار ببرید.
  2. نسخهٔ لازم را ثبت کنید. هم SDK توسعه‌دهنده و هم محیط ساخت باید کد را بشناسند.
  3. قرارداد خطا و null را روشن کنید. نادیده‌گرفتن عملیات همیشه رفتار درست نیست.
  4. خوانایی و سازگاری را مرور کنید. نام متد، اثر جانبی و انتخاب overload برای مصرف‌کننده مهم‌اند.
  5. ادعای کارایی را با داده بسنجید. کوتاه‌ترشدن کد به‌تنهایی نشانهٔ کاهش زمان یا حافظه نیست.
تمرین پیشنهادی

یک نمونه را کپی کنید، پیش از بازکردن خروجی نتیجه را حدس بزنید و بعد فقط یک ورودی را تغییر دهید. در مثال null، شیء را از ابتدا بسازید؛ در مثال عنوان، یک رشتهٔ خالی بدهید. تفاوت نتیجه را با قرارداد تابع توضیح دهید.

اگر در کنار این متدها به سرویس بیرونی وصل می‌شوید، راهنمای اتصال ایمن و امنیت API را هم بخوانید؛ امکانات زبان، کنترل دسترسی و نگهداری امن کلیدها را به‌جای شما انجام نمی‌دهند.

پرسش‌های رایج دربارهٔ متدهای جدید سی‌شارپ

برای یادگیری این نمونه‌ها باید C# 15 نصب کنم؟

خیر. نمونه‌های اصلی روی .NET 10 و C# 14 قابل اجرا هستند. بخش C# 15 صرفاً وضعیت پیش‌نمایش را توضیح می‌دهد.

آیا extension methodهای قبلی دیگر معتبر نیستند؟

معتبرند. بلوک extension امکانات بیشتری برای گروه‌بندی اعضا فراهم می‌کند؛ مهاجرت اجباریِ همهٔ کدهای قدیمی از این موضوع نتیجه نمی‌شود.

کد را ویرایش کنم، دکمهٔ خروجی نتیجهٔ تازه می‌دهد؟

خیر. خروجیِ ثبت‌شده مربوط به کد اصلی و ورودی ثابت همان مثال است. برای اجرای نسخهٔ تغییرکرده از پروژهٔ کنسولی خودتان استفاده کنید.

آیا params ReadOnlySpan همیشه سریع‌تر از params آرایه است؟

چنین تضمینی وجود ندارد. اثر واقعی به شکل استفاده، طول داده و محیط اجرا وابسته است.

خطای شناخته‌نشدن extension یا field را چگونه پیدا کنم؟

ابتدا SDK انتخاب‌شده و نسخهٔ زبان پروژه را بررسی کنید. مثال را در پروژهٔ تازهٔ net10.0 اجرا کنید و سپس تفاوت تنظیمات پروژهٔ اصلی را پیدا کنید.

منابع و یادداشت نویسنده

این راهنما در وب‌سایت شخصی علی خان بابایی منتشر می‌شود. مبنای توضیح‌های نسخه‌ای، مستندات رسمی مایکروسافت است و مثال‌ها برای آموزش این نوشته طراحی شده‌اند؛ این مطلب ادعای افزایش قطعی کارایی یا ارائهٔ یک چارچوب آمادهٔ تولید ندارد.

آخرین بازبینی منابع: ۱۷ اکتبر ۲۰۲۶. تصویر شاخص یک تصویر مفهومی تولیدشده با هوش مصنوعی است.