فصل ۱: تاریخچه و قابلیتهای C# از نسخهٔ 12 تا C# 2
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
قابلیتهای جدید C# 12
عبارتهای مجموعه (Collection expressions)
بهجای مقداردهی اولیهٔ یک آرایه به شکل زیر:
char[] vowels = {'a','e','i','o','u'};
اکنون میتوانید از براکتهای مربعی (یک عبارت مجموعه) استفاده کنید:
char[] vowels = ['a','e','i','o','u'];
عبارتهای مجموعه دو مزیت اصلی دارند. نخست اینکه همین نحو با نوعهای دیگر مجموعه، مانند listها و setها (و حتی نوعهای سطح پایین span) نیز کار میکند:
List<char> list = ['a','e','i','o','u'];
HashSet<char> set = ['a','e','i','o','u'];
ReadOnlySpan<char> span = ['a','e','i','o','u'];
دوم اینکه این عبارتها target-typed هستند؛ یعنی در سناریوهای دیگری که کامپایلر میتواند نوع را استنباط کند، مانند هنگام فراخوانی متدها، میتوانید نوع را حذف کنید:
Foo (['a','e','i','o','u']);
void Foo (char[] letters) { ... }
برای جزئیات بیشتر، بخش «Collection Initializers and Collection Expressions» در صفحهٔ 205 را ببینید.
سازندههای اصلی در کلاسها و structها (Primary constructors in classes and structs)
از C# 12 میتوانید فهرست پارامترها را مستقیماً پس از اعلان یک کلاس (یا struct) قرار دهید:
class Person (string firstName, string lastName)
{
public void Print() => Console.WriteLine (firstName + " " + lastName);
}
این کار به کامپایلر دستور میدهد یک سازندهٔ اصلی را بهطور خودکار بسازد و در نتیجه کد زیر ممکن میشود:
Person p = new Person ("Alice", "Jones");
p.Print(); // Alice Jones
این قابلیت از C# 9 برای recordها وجود داشته است، اما رفتار آن در recordها اندکی متفاوت است. در recordها، کامپایلر بهطور پیشفرض برای هر پارامتر سازندهٔ اصلی یک property عمومیِ فقط-init تولید میکند. در کلاسها و structها چنین نیست؛ برای رسیدن به همان نتیجه باید آن propertyها را صریحاً تعریف کنید:
class Person (string firstName, string lastName)
{
public string FirstName { get; set; } = firstName;
public string LastName { get; set; } = lastName;
}
سازندههای اصلی در سناریوهای ساده بهخوبی کار میکنند. ظرافتها و محدودیتهای آنها را در بخش «Primary Constructors (C# 12)» در صفحهٔ 119 توضیح میدهیم.
پارامترهای پیشفرض لامبدا (Default lambda parameters)
همانگونه که متدهای عادی میتوانند پارامترهایی با مقدار پیشفرض تعریف کنند:
void Print (string message = "") => Console.WriteLine (message);
عبارتهای لامبدا نیز میتوانند همین کار را انجام دهند:
var print = (string message = "") => Console.WriteLine (message);
print ("Hello");
print ();
این قابلیت برای کتابخانههایی مانند ASP.NET Minimal API مفید است.
نام مستعار برای هر نوع (Alias any type)
C# همیشه اجازه داده است با دستور using برای یک نوع ساده یا generic نام مستعار تعریف کنید:
using ListOfInt = System.Collections.Generic.List<int>;
var list = new ListOfInt();
از C# 12، این رویکرد با انواع دیگر نوعها، مانند آرایهها و tupleها، نیز کار میکند:
using NumberList = double[];
using Point = (int X, int Y);
NumberList numbers = { 2.5, 3.5 };
Point p = (3, 4);
قابلیتهای جدید دیگر
C# 12 همچنین از آرایههای درونخطی (inline arrays) از طریق صفت [System.Runtime.CompilerServices.InlineArray] پشتیبانی میکند. این قابلیت ساخت آرایههایی با اندازهٔ ثابت را در یک struct، بدون نیاز به context ناامن، ممکن میسازد و در درجهٔ اول برای استفاده درون APIهای زمان اجرا در نظر گرفته شده است.
قابلیتهای جدید C# 11
C# 11 همراه Visual Studio 2022 عرضه شد و هنگامی که .NET 7 را هدف میگیرید بهطور پیشفرض استفاده میشود.
literalهای رشتهای خام (Raw string literals)
قرار دادن یک رشته درون سه یا چند نویسهٔ نقلقول، یک raw string literal ایجاد میکند که میتواند تقریباً هر دنبالهای از نویسهها را بدون escape کردن یا دوبرابر کردن در خود داشته باشد. این موضوع نمایش literalهای JSON، XML و HTML و نیز عبارتهای منظم و کد منبع را آسان میکند:
string raw = """<file path="c:\temp\test.txt"></file>""";
raw string literalها میتوانند چندخطی باشند و با پیشوند $ interpolation را بپذیرند:
string multiLineRaw = $"""
Line 1
Line 2
The date and time is {DateTime.Now}
""";
استفاده از دو (یا چند) نویسهٔ $ در پیشوند یک raw string literal، دنبالهٔ interpolation را از یک brace به دو (یا چند) brace تغییر میدهد و به شما اجازه میدهد خود braceها را در رشته قرار دهید:
Console.WriteLine ($$"""{ "TimeStamp": "{{DateTime.Now}}" }""");
// Output: { "TimeStamp": "01/01/2024 12:13:25 PM" }
ظرافتهای این قابلیت را در «Raw string literals (C# 11)» در صفحهٔ 59 و «String interpolation» در صفحهٔ 60 پوشش میدهیم.
رشتههای UTF-8
با پسوند u8، literalهای رشتهای با UTF-8 بهجای UTF-16 رمزگذاری میشوند. این قابلیت برای سناریوهای پیشرفته، مانند پردازش سطح پایین متن JSON در نقاط حساس از نظر کارایی، در نظر گرفته شده است:
ReadOnlySpan<byte> utf8 = "ab→cd"u8; // Arrow symbol consumes 3 bytes
Console.WriteLine (utf8.Length); // 7
نوع زیربنایی ReadOnlySpan<byte> است (فصل 23) که میتوانید با فراخوانی متد ToArray() آن را به آرایهٔ byte تبدیل کنید.
الگوهای فهرست (List patterns)
الگوهای فهرست، دنبالهای از عناصر را در براکتهای مربعی تطبیق میدهند و با هر نوع مجموعهای که قابلشمارش (با propertyی به نام Count یا Length) و قابلاندیسگذاری (با indexer از نوع int یا System.Index) باشد کار میکنند:
int[] numbers = { 0, 1, 2, 3, 4 };
Console.WriteLine (numbers is [0, 1, 2, 3, 4]); // True
یک زیرخط، یک عنصر منفرد با هر مقداری را تطبیق میدهد و دو نقطه صفر یا چند عنصر (یک slice) را تطبیق میدهند:
Console.WriteLine (numbers is [_, 1, .., 4]); // True
یک slice میتواند با الگوی var دنبال شود — برای جزئیات «List Patterns» در صفحهٔ 243 را ببینید.
اعضای الزامی (Required members)
اعمال modifier required به یک field یا property، مصرفکنندگان آن کلاس یا struct را مجبور میکند هنگام ساخت شیء، آن عضو را از طریق object initializer مقداردهی کنند:
Asset a1 = new Asset { Name = "House" }; // OK
Asset a2 = new Asset(); // Error: will not compile!
class Asset { public required string Name; }
با این قابلیت میتوانید از نوشتن سازندههایی با فهرست پارامترهای طولانی پرهیز کنید و subclassing را سادهتر سازید. اگر بخواهید علاوه بر این یک سازنده نیز بنویسید، میتوانید صفت [SetsRequiredMembers] را اعمال کنید تا محدودیت عضو الزامی برای آن سازنده دور زده شود — برای جزئیات «Required members (C# 11)» در صفحهٔ 136 را ببینید.
اعضای static virtual/abstract در اینترفیس
از C# 11، اینترفیسها میتوانند اعضا را بهصورت static virtual یا static abstract اعلان کنند:
public interface IParsable<TSelf>
{
static abstract TSelf Parse (string s);
}
این اعضا بهصورت توابع static در کلاسها یا structها پیادهسازی میشوند و میتوان آنها را بهصورت چندریخت از طریق یک پارامتر نوع محدودشده فراخوانی کرد:
T ParseAny<T> (string s) where T : IParsable<T> => T.Parse (s);
توابع operator نیز میتوانند بهصورت static virtual یا static abstract اعلان شوند.
برای جزئیات، «Static virtual/abstract interface members» در صفحهٔ 153 و «Static Polymorphism» در صفحهٔ 260 را ببینید. همچنین در «Calling Static Virtual/Abstract Interface Members» در صفحهٔ 826 توضیح میدهیم چگونه اعضای static abstract را از طریق reflection فراخوانی کنید.
ریاضیات generic (Generic math)
اینترفیس System.Numerics.INumber<TSelf> (جدید در .NET 7) عملیات حسابی را در همهٔ نوعهای عددی یکپارچه میکند و نوشتن متدهای generic مانند نمونهٔ زیر را ممکن میسازد:
T Sum<T> (T[] numbers) where T : INumber<T>
{
T total = T.Zero;
foreach (T n in numbers)
total += n; // Invokes addition operator for any numeric type
return total;
}
int intSum = Sum (3, 5, 7);
double doubleSum = Sum (3.2, 5.3, 7.1);
decimal decimalSum = Sum (3.2m, 5.3m, 7.1m);
INumber<TSelf> توسط همهٔ نوعهای عددی حقیقی و صحیح در .NET (و همچنین char) پیادهسازی شده و از چندین اینترفیس تشکیل میشود که تعریفهای static abstract برای operatorها، مانند نمونهٔ زیر، دارند:
static abstract TResult operator + (TSelf left, TOther right);
این موضوع را در «Polymorphic Operators» در صفحهٔ 261 و «Generic Math» در صفحهٔ 262 پوشش میدهیم.
قابلیتهای جدید دیگر
نوعی که modifier دسترسی file دارد فقط از داخل همان فایل قابل دسترسی است و برای استفاده در source generatorها در نظر گرفته شده است:
C# 11 همچنین checked operatorها را معرفی کرد (بخش «Checked operators» در صفحهٔ 258 را ببینید) تا بتوان توابع operatorی را تعریف کرد که درون بلوکهای checked فراخوانی میشوند (این قابلیت برای پیادهسازی کامل generic math لازم بود). C# 11 همچنین الزام مقداردهی همهٔ fieldهای یک struct در سازندهٔ آن را آسانتر کرد (بخش «Struct Construction Semantics» در صفحهٔ 142 را ببینید).
در نهایت، نوعهای عدد صحیح با اندازهٔ بومی nint و nuint که در C# 9 برای تطبیق با فضای آدرس فرایند در زمان اجرا (32 یا 64 بیت) معرفی شده بودند، در C# 11 هنگام هدفگیری .NET 7 یا جدیدتر بهبود یافتند. بهطور مشخص، هنگام هدفگیری .NET 7+ تفاوت زمان کامپایل میان این نوعها و نوعهای زیربنایی زمان اجرای آنها (IntPtr و UIntPtr) از میان رفته است. برای بحث کامل، «Native-Sized Integers» در صفحهٔ 266 را ببینید.
قابلیتهای جدید C# 10
C# 10 همراه Visual Studio 2022 عرضه شد و هنگامی به کار میرود که .NET 6 را هدف بگیرید.
namespaceهای file-scoped
در حالت رایج که همهٔ نوعهای یک فایل در یک namespace واحد تعریف میشوند، اعلان file-scoped namespace در C# 10 شلوغی را کم کرده و یک سطح غیرضروری از تورفتگی را حذف میکند:
namespace MyNamespace; // Applies to everything that follows in the file.
class Class1 {} // inside MyNamespace
class Class2 {} // inside MyNamespace
دستور global using
هنگامی که یک دستور using را با کلیدواژهٔ global پیشونددهی کنید، آن دستور بر همهٔ فایلهای پروژه اعمال میشود:
global using System;
global using System.Collection.Generic;
این کار شما را از تکرار همان دستورها در تکتک فایلها بینیاز میکند. دستورهای global using با using static نیز کار میکنند.
افزون بر این، پروژههای .NET 6 اکنون از دستورهای implicit global using پشتیبانی میکنند: اگر عنصر ImplicitUsings در فایل پروژه روی true تنظیم شده باشد، namespaceهای پراستفاده بر اساس نوع پروژهٔ SDK بهطور خودکار import میشوند. برای جزئیات بیشتر، «The global using Directive» در صفحهٔ 96 را ببینید.
تغییر غیرمخرب برای نوعهای anonymous
C# 9 کلیدواژهٔ with را برای انجام تغییر غیرمخرب (nondestructive mutation) روی recordها معرفی کرد. در C# 10، کلیدواژهٔ with با نوعهای anonymous نیز کار میکند:
var a1 = new { A = 1, B = 2, C = 3, D = 4, E = 5 };
var a2 = a1 with { E = 10 };
Console.WriteLine (a2); // { A = 1, B = 2, C = 3, D = 4, E = 10 }
نحو جدید deconstruction
C# 7 نحو deconstruction را برای tupleها (یا هر نوعی که متد Deconstruct داشته باشد) معرفی کرد. C# 10 این نحو را یک گام جلوتر میبرد و اجازه میدهد assignment و declaration را در یک deconstruction ترکیب کنید:
var point = (3, 4);
double x = 0;
(x, double y) = point;
مقداردهی اولیهٔ fieldها و سازندههای بدون پارامتر در structها
از C# 10 میتوانید در structها field initializer و سازندهٔ بدون پارامتر داشته باشید (بخش «Structs» در صفحهٔ 142 را ببینید). این موارد فقط هنگامی اجرا میشوند که سازنده صریحاً فراخوانی شود و بنابراین بهآسانی میتوان از آنها عبور کرد؛ برای مثال از طریق کلیدواژهٔ default. این قابلیت عمدتاً برای record structها معرفی شد.
Record structها
recordها نخستینبار در C# 9 معرفی شدند؛ جایی که مانند یک کلاسِ تقویتشده توسط کامپایلر عمل میکردند. در C# 10، recordها میتوانند struct نیز باشند:
record struct Point (int X, int Y);
قواعد در سایر موارد مشابهاند: record structها تقریباً همان قابلیتهای class recordها را دارند (بخش «Records» در صفحهٔ 227 را ببینید). یک استثنا این است که propertyهای تولیدشده توسط کامپایلر در record structها قابلنوشتناند، مگر اینکه اعلان record را با کلیدواژهٔ readonly پیشونددهی کنید.
بهبودهای عبارتهای لامبدا
نحو پیرامون عبارتهای لامبدا به چند شکل بهبود یافته است. نخست، نوعدهی ضمنی (var) مجاز است:
var greeter = () => "Hello, world";
نوع ضمنی یک عبارت لامبدا یک delegate از نوع Action یا Func است؛ بنابراین greeter در این مثال از نوع Func<string> است. نوع هر پارامتر را باید صریحاً مشخص کنید:
var square = (int x) => x * x;
دوم، یک عبارت لامبدا میتواند نوع بازگشتی را مشخص کند:
var sqr = int (int x) => x;
هدف اصلی این قابلیت، بهبود کارایی کامپایلر در لامبداهای تودرتوی پیچیده است.
سوم، میتوانید یک عبارت لامبدا را به پارامتر متدی از نوع object، Delegate یا Expression ارسال کنید:
M1 (() => "test"); // Implicitly typed to Func<string>
M2 (() => "test"); // Implicitly typed to Func<string>
M3 (() => "test"); // Implicitly typed to Expression<Func<string>>
void M1 (object x) {}
void M2 (Delegate x) {}
void M3 (Expression x) {}
و در نهایت، میتوانید attributeها را روی متد هدفِ تولیدشده توسط کامپایلر برای یک عبارت لامبدا (و همچنین پارامترها و مقدار بازگشتی آن) اعمال کنید:
Action a = [Description("test")] () => { };
برای جزئیات بیشتر، «Applying Attributes to Lambda Expressions» در صفحهٔ 245 را ببینید.
الگوهای property تودرتو
نحو سادهشدهٔ زیر در C# 10 برای تطبیق الگوی property تودرتو مجاز است (بخش «Property Patterns» در صفحهٔ 241 را ببینید):
var obj = new Uri ("https://www.linqpad.net");
if (obj is Uri { Scheme.Length: 5 }) ...
این کد معادل است با:
if (obj is Uri { Scheme: { Length: 5 }}) ...
CallerArgumentExpression
پارامتری از یک متد که attribute [CallerArgumentExpression] روی آن اعمال شده باشد، عبارت آرگومان را از محل فراخوانی capture میکند:
Print (Math.PI * 2);
void Print (double number,
[CallerArgumentExpression("number")] string expr = null)
=> Console.WriteLine (expr);
// Output: Math.PI * 2
این قابلیت عمدتاً برای کتابخانههای اعتبارسنجی و assertion طراحی شده است (بخش «CallerArgumentExpression» در صفحهٔ 247 را ببینید).
قابلیتهای جدید دیگر
دستور #line در C# 10 بهبود یافته تا امکان مشخصکردن ستون و بازه را فراهم کند.
رشتههای interpolated در C# 10 میتوانند constant باشند، مشروط بر اینکه مقدارهای interpolated نیز constant باشند.
recordها در C# 10 میتوانند متد ToString() را seal کنند.
تحلیل definite assignment در C# بهبود یافته است تا عبارتهایی مانند نمونهٔ زیر کار کنند:
if (foo?.TryParse ("123", out var number) ?? false)
Console.WriteLine (number);
(پیش از C# 10، کامپایلر خطای «Use of unassigned local variable ‘number’.» ایجاد میکرد.)
قابلیتهای جدید C# 9.0
C# 9.0 همراه Visual Studio 2019 عرضه شد و هنگامی به کار میرود که .NET 5 را هدف بگیرید.
دستورهای سطح بالا (Top-level statements)
با دستورهای سطح بالا (بخش «Top-Level Statements» در صفحهٔ 41 را ببینید)، میتوانید بدون سربار متد Main و کلاس Program برنامه بنویسید:
using System;
Console.WriteLine ("Hello, world");
دستورهای سطح بالا میتوانند شامل متدها باشند (که مانند متدهای local عمل میکنند). همچنین میتوانید از طریق متغیر «جادویی» args به آرگومانهای خط فرمان دسترسی داشته باشید و مقداری را به فراخواننده بازگردانید. پس از دستورهای سطح بالا میتوان اعلانهای type و namespace قرار داد.
setterهای فقط-init
یک setter فقط-init (بخش «Init-only setters» در صفحهٔ 116 را ببینید) در اعلان property بهجای کلیدواژهٔ set از کلیدواژهٔ init استفاده میکند:
class Foo { public int ID { get; init; } }
این property مانند یک property فقطخواندنی رفتار میکند، با این تفاوت که میتوان آن را از طریق object initializer نیز مقداردهی کرد:
var foo = new Foo { ID = 123 };
این کار ساخت نوعهای تغییرناپذیر (فقطخواندنی) را ممکن میکند که بتوان آنها را بهجای سازنده، از طریق object initializer مقداردهی کرد؛ همچنین کمک میکند از ضدالگوی سازندههایی که تعداد زیادی پارامتر اختیاری میپذیرند پرهیز شود. setterهای فقط-init هنگام استفاده در recordها، تغییر غیرمخرب را نیز ممکن میکنند.
Recordها
record (بخش «Records» در صفحهٔ 227 را ببینید) نوع ویژهای از class است که برای کارکرد مناسب با دادهٔ تغییرناپذیر طراحی شده است. خاصترین قابلیت آن پشتیبانی از تغییر غیرمخرب از طریق کلیدواژهٔ جدید with است:
Point p1 = new Point (2, 3);
Point p2 = p1 with { Y = 4 }; // p2 is a copy of p1, but with Y set to 4
Console.WriteLine (p2); // Point { X = 2, Y = 4 }
record Point
{
public Point (double x, double y) => (X, Y) = (x, y);
public double X { get; init; }
public double Y { get; init; }
}
در موارد ساده، record میتواند کد تکراریِ تعریف propertyها و نوشتن سازنده و deconstructor را نیز حذف کند. میتوانیم تعریف record مربوط به Point را بدون از دست رفتن هیچ قابلیتی با کد زیر جایگزین کنیم:
record Point (double X, double Y);
recordها مانند tupleها بهطور پیشفرض برابری ساختاری (structural equality) دارند. recordها میتوانند از recordهای دیگر ارثبری کنند و همان سازههایی را که classها میتوانند داشته باشند در خود جای دهند. کامپایلر recordها را در زمان اجرا بهصورت class پیادهسازی میکند.
بهبودهای pattern matching
الگوی رابطهای (بخش «Patterns» در صفحهٔ 238 را ببینید) اجازه میدهد عملگرهای <، >، <= و >= در الگوها ظاهر شوند:
string GetWeightCategory (decimal bmi) => bmi switch {
< 18.5m => "underweight",
< 25m => "normal",
< 30m => "overweight",
_ => "obese" };
با pattern combinatorها میتوانید الگوها را از طریق سه کلیدواژهٔ جدید and، or و not ترکیب کنید:
bool IsVowel (char c) => c is 'a' or 'e' or 'i' or 'o' or 'u';
bool IsLetter (char c) => c is >= 'a' and <= 'z'
or >= 'A' and <= 'Z';
همانند عملگرهای && و ||، and اولویت بالاتری از or دارد. میتوانید این ترتیب را با پرانتز تغییر دهید.
combinator not را میتوان همراه الگوی type برای بررسی اینکه یک شیء از یک نوع نیست به کار برد:
if (obj is not string) ...
عبارتهای new با نوعِ هدف (Target-typed new expressions)
هنگام ساخت یک شیء، C# 9 اجازه میدهد در صورتی که کامپایلر بتواند نوع را بدون ابهام استنباط کند، نام نوع را حذف کنید:
System.Text.StringBuilder sb1 = new();
System.Text.StringBuilder sb2 = new ("Test");
این قابلیت بهویژه زمانی مفید است که اعلان متغیر و مقداردهی اولیهٔ آن در بخشهای متفاوتی از کد قرار دارند:
class Foo
{
System.Text.StringBuilder sb;
public Foo (string initialValue) => sb = new (initialValue);
}
و در سناریوی زیر:
MyMethod (new ("test"));
void MyMethod (System.Text.StringBuilder sb) { ... }
برای اطلاعات بیشتر «Target-Typed new Expressions» در صفحهٔ 77 را ببینید.
بهبودهای interoperability
C# 9 اشارهگرهای تابع (function pointers) را معرفی میکند (بخشهای «Function Pointers» در صفحهٔ 268 و «Callbacks with Function Pointers» در صفحهٔ 991 را ببینید). هدف اصلی آنها این است که به کد unmanaged اجازه دهند متدهای static در C# را بدون سربار یک نمونهٔ delegate فراخوانی کند؛ همچنین وقتی آرگومانها و نوعهای بازگشتی blittable باشند (در هر دو سمت بهشکل یکسان نمایش داده شوند)، امکان عبور از لایهٔ P/Invoke وجود دارد.
C# 9 همچنین نوعهای عدد صحیح با اندازهٔ بومی nint و nuint را معرفی میکند (بخش «Native-Sized Integers» در صفحهٔ 266 را ببینید) که در زمان اجرا به System.IntPtr و System.UIntPtr نگاشت میشوند. در زمان کامپایل، مانند نوعهای عددی و با پشتیبانی از عملیات حسابی رفتار میکنند.
قابلیتهای جدید دیگر
علاوه بر این، C# 9 اکنون به شما اجازه میدهد:
- یک متد یا property فقطخواندنی را override کنید بهگونهای که نوع مشتقشدهتری را بازگرداند (بخش «Covariant return types» در صفحهٔ 131).
- attributeها را روی توابع local اعمال کنید (بخش «Attributes» در صفحهٔ 243).
- کلیدواژهٔ
static را روی عبارتهای لامبدا یا توابع local اعمال کنید تا مطمئن شوید بهطور ناخواسته متغیرهای local یا instance را capture نمیکنید (بخش «Static lambdas» در صفحهٔ 192).
- با نوشتن یک extension method به نام
GetEnumerator هر نوعی را با دستور foreach سازگار کنید.
- با اعمال attribute
[ModuleInitializer] به یک متد static void بدون پارامتر، متد module initializer تعریف کنید که هنگام نخستین بارگذاری یک assembly یکبار اجرا شود.
- از یک «discard» (نماد زیرخط) بهعنوان آرگومان عبارت لامبدا استفاده کنید.
- متدهای partial توسعهیافتهای بنویسید که پیادهسازی آنها اجباری است و سناریوهایی مانند source generatorهای جدید Roslyn را ممکن میکنند (بخش «Extended partial methods» در صفحهٔ 125).
- attributeای را روی متدها، نوعها یا moduleها اعمال کنید تا از مقداردهی اولیهٔ متغیرهای local توسط زمان اجرا جلوگیری شود (بخش «[SkipLocalsInit]» در صفحهٔ 269).
قابلیتهای جدید C# 8.0
C# 8.0 نخستینبار همراه Visual Studio 2019 عرضه شد و امروزه نیز هنگامی استفاده میشود که .NET Core 3 یا .NET Standard 2.1 را هدف بگیرید.
اندیسها و بازهها (Indices and ranges)
اندیسها و بازهها کار با عناصر یا بخشهایی از یک آرایه (یا نوعهای سطح پایین Span<T> و ReadOnlySpan<T>) را ساده میکنند.
اندیسها با استفاده از عملگر ^ اجازه میدهند عناصر را نسبت به انتهای آرایه ارجاع دهید. ^1 به آخرین عنصر، ^2 به عنصر یکیماندهبهآخر و به همین ترتیب اشاره میکند:
char[] vowels = new char[] {'a','e','i','o','u'};
char lastElement = vowels [^1]; // 'u'
char secondToLast = vowels [^2]; // 'o'
بازهها با استفاده از عملگر .. اجازه میدهند یک آرایه را «slice» کنید:
char[] firstTwo = vowels [..2]; // 'a', 'e'
char[] lastThree = vowels [2..]; // 'i', 'o', 'u'
char[] middleOne = vowels [2..3] // 'i'
char[] lastTwo = vowels [^2..]; // 'o', 'u'
C# اندیسها و بازهها را با کمک نوعهای Index و Range پیادهسازی میکند:
Index last = ^1;
Range firstTwoRange = 0..2;
char[] firstTwo = vowels [firstTwoRange]; // 'a', 'e'
میتوانید با تعریف یک indexer با پارامتر از نوع Index یا Range از اندیسها و بازهها در کلاسهای خودتان پشتیبانی کنید:
class Sentence
{
string[] words = "The quick brown fox".Split();
public string this [Index index] => words [index];
public string[] this [Range range] => words [range];
}
برای اطلاعات بیشتر «Indices and Ranges» در صفحهٔ 63 را ببینید.
assignment همجوشی null (Null-coalescing assignment)
عملگر ??= فقط در صورتی یک متغیر را مقداردهی میکند که مقدار آن null باشد. بهجای:
if (s == null) s = "Hello, world";
اکنون میتوانید بنویسید:
اعلانهای using
اگر براکتها و بلوک دستوری پس از یک دستور using را حذف کنید، آن دستور به یک using declaration تبدیل میشود. در این حالت، resource هنگامی dispose میشود که اجرا از بلوک دستوری پیرامونی خارج شود:
if (File.Exists ("file.txt"))
{
using var reader = File.OpenText ("file.txt");
Console.WriteLine (reader.ReadLine());
...
}
در این مثال، reader هنگامی dispose میشود که اجرا از بلوک دستور if خارج شود.
اعضای فقطخواندنی
C# 8 اجازه میدهد modifier readonly را روی توابع یک struct اعمال کنید تا تضمین شود اگر تابع تلاش کند fieldی را تغییر دهد، یک خطای زمان کامپایل ایجاد شود:
struct Point
{
public int X, Y;
public readonly void ResetX() => X = 0; // Error!
}
اگر یک تابع readonly تابعی non-readonly را فراخوانی کند، کامپایلر هشدار ایجاد میکند (و برای جلوگیری از احتمال mutation، بهصورت دفاعی از struct کپی میگیرد).
متدهای local static
افزودن modifier static به یک متد local مانع میشود آن متد متغیرها و پارامترهای local متد پیرامونی را ببیند. این کار coupling را کاهش میدهد و به متد local اجازه میدهد متغیرهای خود را آزادانه اعلان کند، بدون خطر برخورد با متغیرهای متد حاوی آن.
اعضای پیشفرض اینترفیس
C# 8 اجازه میدهد برای یک عضو interface پیادهسازی پیشفرض اضافه کنید و در نتیجه پیادهسازی آن عضو اختیاری شود:
interface ILogger
{
void Log (string text) => Console.WriteLine (text);
}
این یعنی میتوانید بدون شکستن پیادهسازیهای موجود، عضوی را به یک interface اضافه کنید. پیادهسازیهای پیشفرض باید صریحاً از طریق interface فراخوانی شوند:
((ILogger)new Logger()).Log ("message");
interfaceها همچنین میتوانند اعضای static (از جمله fieldها) تعریف کنند که از کد درون پیادهسازیهای پیشفرض قابل دسترسیاند:
interface ILogger
{
void Log (string text) => Console.WriteLine (Prefix + text);
static string Prefix = "";
}
یا از بیرون interface قابل دسترسیاند، مگر اینکه از طریق یک accessibility modifier روی عضو static interface، مانند private، protected یا internal، محدود شده باشند:
ILogger.Prefix = "File log: ";
fieldهای instance مجاز نیستند. برای جزئیات بیشتر «Default Interface Members» در صفحهٔ 151 را ببینید.
عبارتهای switch
از C# 8 میتوانید switch را در context یک عبارت استفاده کنید:
string cardName = cardNumber switch // assuming cardNumber is an int
{
13 => "King",
12 => "Queen",
11 => "Jack",
_ => "Pip card" // equivalent to 'default'
};
برای نمونههای بیشتر «Switch expressions» در صفحهٔ 90 را ببینید.
الگوهای tuple، positional و property
C# 8 از سه الگوی جدید پشتیبانی میکند که عمدتاً برای دستورها/عبارتهای switch سودمندند (بخش «Patterns» در صفحهٔ 238 را ببینید). الگوهای tuple اجازه میدهند روی چند مقدار switch کنید:
int cardNumber = 12; string suite = "spades";
string cardName = (cardNumber, suite) switch
{
(13, "spades") => "King of spades",
(13, "clubs") => "King of clubs",
...
};
الگوهای positional نحو مشابهی را برای اشیایی که deconstructor ارائه میکنند ممکن میسازند، و الگوهای property اجازه میدهند بر اساس propertyهای یک شیء تطبیق انجام دهید. همهٔ این الگوها را هم در switchها و هم همراه عملگر is میتوانید به کار ببرید. نمونهٔ زیر از یک الگوی property استفاده میکند تا بررسی کند آیا obj رشتهای با طول 4 است:
if (obj is string { Length:4 }) ...
نوعهای reference nullable
در حالی که نوعهای value nullable قابلیت null بودن را به value typeها میآورند، نوعهای reference nullable عکس آن عمل میکنند و درجهای از non-nullability را به reference typeها اضافه میکنند؛ هدف آنها کمک به جلوگیری از NullReferenceException است. نوعهای reference nullable سطحی از ایمنی را معرفی میکنند که کاملاً توسط کامپایلر، بهشکل هشدار یا خطا در زمان تشخیص کدی که در معرض تولید NullReferenceException است، اعمال میشود.
نوعهای reference nullable را میتوان در سطح پروژه (از طریق عنصر Nullable در فایل پروژهٔ .csproj) یا در کد (با دستور #nullable) فعال کرد. پس از فعالشدن، کامپایلر non-nullability را پیشفرض میگیرد: اگر میخواهید یک reference type مقدار null را بپذیرد، باید پسوند ? را اعمال کنید تا یک reference type nullable مشخص شود:
#nullable enable // Enable nullable reference types from this point on
string s1 = null; // Generates a compiler warning! (s1 is non-nullable)
string? s2 = null; // OK: s2 is nullable reference type
fieldهای مقداردهینشده نیز (اگر نوع nullable علامتگذاری نشده باشد) هشدار تولید میکنند، همانگونه که dereference کردن یک reference type nullable در صورتی که کامپایلر احتمال وقوع NullReferenceException را تشخیص دهد هشدار میدهد:
void Foo (string? s) => Console.Write (s.Length); // Warning (.Length)
برای حذف هشدار میتوانید از عملگر null-forgiving (!) استفاده کنید:
void Foo (string? s) => Console.Write (s!.Length);
برای بحث کامل، «Nullable Reference Types» در صفحهٔ 215 را ببینید.
جریانهای ناهمگام (Asynchronous streams)
پیش از C# 8 میتوانستید از yield return برای نوشتن iterator یا از await برای نوشتن تابع ناهمگام استفاده کنید. اما نمیتوانستید هر دو را با هم به کار ببرید و iteratorی بنویسید که await کند و عناصر را بهصورت ناهمگام yield دهد. C# 8 این مشکل را با معرفی asynchronous streamها برطرف میکند:
async IAsyncEnumerable<int> RangeAsync (
int start, int count, int delay)
{
for (int i = start; i < start + count; i++)
{
await Task.Delay (delay);
yield return i;
دستور await foreach یک asynchronous stream را مصرف میکند:
await foreach (var number in RangeAsync (0, 10, 100))
Console.WriteLine (number);
برای اطلاعات بیشتر «Asynchronous Streams» در صفحهٔ 672 را ببینید.
قابلیتهای جدید C# 7.x
C# 7.x نخستینبار همراه Visual Studio 2017 عرضه شد. C# 7.3 امروزه نیز توسط Visual Studio 2019 هنگامی استفاده میشود که .NET Core 2، .NET Framework 4.6 تا 4.8 یا .NET Standard 2.0 را هدف بگیرید.
C# 7.3
C# 7.3 بهبودهای کوچکی در قابلیتهای موجود ایجاد کرد؛ مانند امکان استفاده از عملگرهای برابری با tupleها، بهبود overload resolution، و امکان اعمال attributeها روی backing fieldهای propertyهای automatic:
[field:NonSerialized]
public int MyProperty { get; set; }
C# 7.3 همچنین قابلیتهای پیشرفتهٔ برنامهنویسی با تخصیص حافظهٔ کم در C# 7.2 را توسعه داد؛ با امکان assignment مجدد ref localها، حذف نیاز به pin کردن هنگام index کردن fieldهای fixed، و پشتیبانی از field initializer همراه stackalloc:
int* pointer = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};
توجه کنید که حافظهٔ تخصیصیافته روی stack را میتوان مستقیماً به یک Span<T> نسبت داد. spanها و دلیل استفاده از آنها را در فصل 23 توضیح میدهیم.
C# 7.2
C# 7.2 modifier جدید private protected (اشتراک internal و protected)، امکان قرار دادن آرگومانهای positional پس از آرگومانهای named هنگام فراخوانی متدها، و structهای readonly را اضافه کرد. یک struct readonly تضمین میکند همهٔ fieldها readonly باشند تا قصد برنامهنویس روشن شود و کامپایلر آزادی بیشتری برای بهینهسازی داشته باشد:
readonly struct Point
{
public readonly int X, Y; // X and Y must be readonly
}
C# 7.2 همچنین قابلیتهای تخصصی برای کمک به micro-optimization و برنامهنویسی کمتخصیص افزود: «The in modifier» در صفحهٔ 72، «Ref Locals» در صفحهٔ 75، «Ref Returns» در صفحهٔ 76 و «Ref Structs» در صفحهٔ 144 را ببینید.
C# 7.1
از C# 7.1، هنگام استفاده از کلیدواژهٔ default در صورتی که نوع قابل استنباط باشد میتوانید نوع را حذف کنید:
decimal number = default; // number is decimal
C# 7.1 همچنین قواعد دستورهای switch را آسانتر کرد (تا بتوان روی generic type parameterها pattern match انجام داد)، اجازه داد متد Main برنامه ناهمگام باشد، و اجازه داد نام عناصر tuple استنباط شوند:
var now = DateTime.Now;
var tuple = (now.Hour, now.Minute, now.Second);
بهبودهای literalهای عددی
literalهای عددی در C# 7 میتوانند برای بهبود خوانایی شامل زیرخط باشند. اینها digit separator نامیده میشوند و کامپایلر آنها را نادیده میگیرد:
literalهای دودویی را میتوان با پیشوند 0b مشخص کرد:
var b = 0b1010_1011_1100_1101_1110_1111;
متغیرهای out و discardها
C# 7 فراخوانی متدهایی را که پارامتر out دارند آسانتر میکند. نخست، اکنون میتوانید متغیرهای out را همان لحظه اعلان کنید (بخش «Out variables and discards» در صفحهٔ 72 را ببینید):
bool successful = int.TryParse ("123", out int result);
Console.WriteLine (result);
و هنگام فراخوانی متدی با چند پارامتر out، میتوانید مواردی را که به آنها علاقه ندارید با نویسهٔ زیرخط discard کنید:
SomeBigMethod (out _, out _, out _, out int x, out _, out _, out _);
Console.WriteLine (x);
الگوهای type و متغیرهای pattern
همچنین میتوانید با عملگر is متغیرها را همان لحظه معرفی کنید. اینها pattern variable نامیده میشوند (بخش «Introducing a pattern variable» در صفحهٔ 130 را ببینید):
void Foo (object x)
{
if (x is string s)
Console.WriteLine (s.Length);
}
دستور switch نیز از type patternها پشتیبانی میکند؛ بنابراین میتوانید علاوه بر constantها بر اساس type نیز switch کنید (بخش «Switching on types» در صفحهٔ 89 را ببینید). میتوانید با clause when شرط مشخص کنید و همچنین روی مقدار null switch کنید:
switch (x)
{
case int i:
Console.WriteLine ("It's an int!");
break;
case string s:
Console.WriteLine (s.Length); // We can use the s variable
break;
case bool b when b == true: // Matches only when b is true
Console.WriteLine ("True");
break;
case null:
Console.WriteLine ("Nothing");
break;
}
متدهای local
متد local متدی است که داخل یک تابع دیگر اعلان میشود (بخش «Local methods» در صفحهٔ 106 را ببینید):
void WriteCubes()
{
Console.WriteLine (Cube (3));
Console.WriteLine (Cube (4));
Console.WriteLine (Cube (5));
int Cube (int value) => value * value * value;
}
متدهای local فقط برای تابع حاوی خود قابل مشاهدهاند و میتوانند متغیرهای local را همانند عبارتهای لامبدا capture کنند.
اعضای expression-bodied بیشتر
C# 6 نحو expression-bodied با «fat-arrow» را برای متدها، propertyهای فقطخواندنی، operatorها و indexerها معرفی کرد. C# 7 این قابلیت را به سازندهها، propertyهای خواندنی/نوشتنی و finalizerها گسترش میدهد:
public class Person
{
string name;
public Person (string name) => Name = name;
public string Name
{
get => name;
set => name = value ?? "";
}
~Person () => Console.WriteLine ("finalize");
}
Deconstructorها
C# 7 الگوی deconstructor را معرفی میکند (بخش «Deconstructors» در صفحهٔ 110 را ببینید). در حالی که یک constructor معمولاً مجموعهای از مقدارها را بهعنوان پارامتر میگیرد و آنها را به fieldها نسبت میدهد، deconstructor عکس این کار را انجام میدهد و fieldها را دوباره به مجموعهای از متغیرها نسبت میدهد. اگر مدیریت استثنا را کنار بگذاریم، میتوانیم برای کلاس Person در مثال قبلی deconstructor زیر را بنویسیم:
public void Deconstruct (out string firstName, out string lastName)
{
int spacePos = name.IndexOf (' ');
firstName = name.Substring (0, spacePos);
lastName = name.Substring (spacePos + 1);
}
deconstructorها با نحو ویژهٔ زیر فراخوانی میشوند:
var joe = new Person ("Joe Bloggs");
var (first, last) = joe; // Deconstruction
Console.WriteLine (first); // Joe
Console.WriteLine (last); // Bloggs
Tupleها
شاید برجستهترین بهبود C# 7، پشتیبانی صریح از tupleها باشد (بخش «Tuples» در صفحهٔ 222 را ببینید). tupleها راهی ساده برای ذخیرهٔ مجموعهای از مقدارهای مرتبط فراهم میکنند:
var bob = ("Bob", 23);
Console.WriteLine (bob.Item1); // Bob
Console.WriteLine (bob.Item2); // 23
tupleهای جدید C# در واقع syntax sugar برای استفاده از structهای generic از نوع System.ValueTuple<…> هستند. اما به کمک قابلیتهای کامپایلر، عناصر tuple میتوانند نام داشته باشند:
var tuple = (name:"Bob", age:23);
Console.WriteLine (tuple.name); // Bob
Console.WriteLine (tuple.age); // 23
با tupleها، توابع میتوانند بدون توسل به پارامترهای out یا افزودن سربار یک type اضافی، چند مقدار بازگردانند:
static (int row, int column) GetFilePosition() => (3, 10);
static void Main()
{
var pos = GetFilePosition();
Console.WriteLine (pos.row); // 3
Console.WriteLine (pos.column); // 10
}
tupleها بهطور ضمنی از الگوی deconstruction پشتیبانی میکنند، بنابراین میتوانید بهآسانی آنها را به متغیرهای جداگانه deconstruct کنید:
static void Main()
{
(int row, int column) = GetFilePosition(); // Creates 2 local variables
Console.WriteLine (row); // 3
Console.WriteLine (column); // 10
}
عبارتهای throw
پیش از C# 7، throw همیشه یک statement بود. اکنون میتواند بهصورت expression نیز در توابع expression-bodied ظاهر شود:
public string Foo() => throw new NotImplementedException();
یک throw expression همچنین میتواند در یک ternary conditional expression ظاهر شود:
string Capitalize (string value) =>
value == null ? throw new ArgumentException ("value") :
value == "" ? "" :
char.ToUpper (value[0]) + value.Substring (1);
قابلیتهای جدید C# 6.0
C# 6.0 که همراه Visual Studio 2015 عرضه شد، دارای کامپایلری از نسل جدید است که بهطور کامل با C# نوشته شده است. این کامپایلر که با نام پروژهٔ «Roslyn» شناخته میشود، کل pipeline کامپایل را از طریق کتابخانهها در دسترس قرار میدهد و به شما امکان میدهد روی کد منبع دلخواه تحلیل کد انجام دهید. خود کامپایلر متنباز است و کد منبع آن در github.com/dotnet/roslyn موجود است.
علاوه بر این، C# 6.0 چندین بهبود کوچک اما مهم دارد که هدف اصلی آنها کاهش شلوغی کد است.
عملگر null-conditional («Elvis») (بخش «Null Operators» در صفحهٔ 82 را ببینید) نیاز به بررسی صریح null پیش از فراخوانی متد یا دسترسی به عضو type را از میان میبرد. در مثال زیر، result بهجای پرتاب NullReferenceException برابر null میشود:
System.Text.StringBuilder sb = null;
string result = sb?.ToString(); // result is null
توابع expression-bodied (بخش «Methods» در صفحهٔ 106 را ببینید) اجازه میدهند متدها، propertyها، operatorها و indexerهایی که از یک expression تشکیل شدهاند، بهشکل کوتاهتر و با سبک عبارت لامبدا نوشته شوند:
public int TimesTwo (int x) => x * 2;
public string SomeProperty => "Property value";
property initializerها (فصل 3) اجازه میدهند به یک property خودکار مقدار اولیه اختصاص دهید:
public DateTime TimeCreated { get; set; } = DateTime.Now;
propertyهای مقداردهیشده همچنین میتوانند فقطخواندنی باشند:
public DateTime TimeCreated { get; } = DateTime.Now;
propertyهای فقطخواندنی را همچنین میتوان در سازنده مقداردهی کرد؛ موضوعی که ساخت typeهای تغییرناپذیر (فقطخواندنی) را آسانتر میکند.
index initializerها (فصل 4) مقداردهی اولیهٔ یکمرحلهای هر typeای را که indexer ارائه کند ممکن میسازند:
var dict = new Dictionary<int,string>()
{
[3] = "three",
[10] = "ten"
};
string interpolation (بخش «String Type» در صفحهٔ 58 را ببینید) جایگزینی کوتاه برای string.Format ارائه میکند:
string s = $"It is {DateTime.Now.DayOfWeek} today";
exception filterها (بخش «try Statements and Exceptions» در صفحهٔ 195 را ببینید) اجازه میدهند شرطی را روی یک بلوک catch اعمال کنید:
string html;
try
{
html = await new HttpClient().GetStringAsync ("http://asef");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
...
}
دستور using static (بخش «Namespaces» در صفحهٔ 95 را ببینید) اجازه میدهد همهٔ اعضای static یک type را import کنید تا بتوانید آن اعضا را بدون qualification به کار ببرید:
using static System.Console;
...
WriteLine ("Hello, world"); // WriteLine instead of Console.WriteLine
عملگر nameof (فصل 3) نام یک متغیر، type یا symbol دیگر را بهصورت رشته بازمیگرداند. این کار مانع شکستن کد هنگام rename کردن یک symbol در Visual Studio میشود:
int capacity = 123;
string x = nameof (capacity); // x is "capacity"
string y = nameof (Uri.Host); // y is "Host"
و در نهایت، اکنون اجازه دارید درون بلوکهای catch و finally از await استفاده کنید.
قابلیتهای جدید C# 5.0
قابلیت بزرگ جدید C# 5.0 پشتیبانی از توابع ناهمگام از طریق دو کلیدواژهٔ جدید async و await بود. توابع ناهمگام continuationهای ناهمگام را ممکن میکنند که نوشتن برنامههای rich-client پاسخگو و thread-safe را آسانتر میسازد. همچنین نوشتن برنامههای I/O-bound بسیار همروند و کارآمد را آسان میکنند؛ برنامههایی که برای هر عملیات یک resource از نوع thread را اشغال نمیکنند. توابع ناهمگام را با جزئیات در فصل 14 پوشش میدهیم.
قابلیتهای جدید C# 4.0
C# 4.0 چهار بهبود عمده معرفی کرد:
Dynamic binding (فصلهای 4 و 19) binding — فرایند resolve کردن typeها و memberها — را از زمان کامپایل به زمان اجرا موکول میکند و در سناریوهایی مفید است که در غیر این صورت به کد پیچیدهٔ reflection نیاز دارند. Dynamic binding همچنین هنگام تعامل با زبانهای پویا و componentهای COM سودمند است.
پارامترهای اختیاری (فصل 2) اجازه میدهند توابع مقدارهای پیشفرض برای پارامترها مشخص کنند تا فراخواننده بتواند آرگومانها را حذف کند؛ و آرگومانهای named اجازه میدهند فراخوانندهٔ تابع یک آرگومان را بهجای موقعیت، با نام شناسایی کند.
قواعد type variance در C# 4.0 آسانتر شدند (فصلهای 3 و 4)، بهگونهای که type parameterها در generic interfaceها و generic delegateها میتوانند بهصورت covariant یا contravariant علامتگذاری شوند و تبدیلهای type طبیعیتری را ممکن سازند.
تعاملپذیری COM (فصل 24) در C# 4.0 از سه جهت بهبود یافت. نخست، آرگومانها را میتوان بدون کلیدواژهٔ ref با reference ارسال کرد (بهویژه همراه پارامترهای اختیاری سودمند است). دوم، assemblyهایی که typeهای COM interop دارند میتوانند بهجای reference شدن، link شوند. typeهای interop لینکشده از type equivalence پشتیبانی میکنند، نیاز به Primary Interop Assemblyها را حذف میکنند و مشکلات versioning و deployment را پایان میدهند. سوم، توابعی که typeهای COM Variant را از typeهای interop لینکشده بازمیگردانند، بهجای object به dynamic نگاشت میشوند و نیاز به casting را از میان میبرند.
قابلیتهای جدید C# 3.0
قابلیتهای افزودهشده به C# 3.0 عمدتاً حول تواناییهای Language-Integrated Query (LINQ) متمرکز بودند. LINQ اجازه میدهد queryها مستقیماً داخل یک برنامهٔ C# نوشته شوند، درستی آنها بهصورت ایستا بررسی شود، و هم مجموعههای محلی (مانند listها یا سندهای XML) و هم منبعهای دادهٔ راهدور (مانند پایگاه داده) مورد query قرار گیرند. قابلیتهای C# 3.0 که برای پشتیبانی از LINQ اضافه شدند شامل متغیرهای local با type ضمنی، typeهای anonymous، object initializerها، عبارتهای لامبدا، extension methodها، query expressionها و expression treeها بودند.
متغیرهای local با type ضمنی (کلیدواژهٔ var، فصل 2) اجازه میدهند type متغیر را در declaration حذف کنید تا کامپایلر آن را استنباط کند. این کار شلوغی را کاهش میدهد و همچنین استفاده از typeهای anonymous (فصل 4) را ممکن میسازد؛ کلاسهای سادهای که همان لحظه ساخته میشوند و معمولاً در خروجی نهایی queryهای LINQ به کار میروند. آرایهها نیز میتوانند type ضمنی داشته باشند (فصل 2).
object initializerها (فصل 3) ساخت شیء را ساده میکنند، زیرا اجازه میدهند propertyها را بهصورت inline پس از فراخوانی سازنده تنظیم کنید. object initializerها هم با typeهای named و هم anonymous کار میکنند.
عبارتهای لامبدا (فصل 4) توابع کوچکی هستند که کامپایلر همان لحظه آنها را میسازد؛ این عبارتها بهویژه در queryهای «fluent» LINQ (فصل 8) مفیدند.
extension methodها (فصل 4) یک type موجود را با متدهای جدید گسترش میدهند (بدون تغییر تعریف type) و باعث میشوند متدهای static مانند متدهای instance به نظر برسند. query operatorهای LINQ بهصورت extension method پیادهسازی شدهاند.
query expressionها (فصل 8) نحوی سطحبالاتر برای نوشتن queryهای LINQ فراهم میکنند که هنگام کار با چند sequence یا range variable میتواند بهطور چشمگیری سادهتر باشد.
expression treeها (فصل 8) مدلهای شیء سند کد (Code Document Object Model یا DOM) کوچکی هستند که عبارتهای لامبدای نسبتدادهشده به type ویژهٔ Expression<TDelegate> را توصیف میکنند. expression treeها اجرای راهدور queryهای LINQ را ممکن میکنند (برای مثال روی یک سرور پایگاه داده)، زیرا میتوان در زمان اجرا درون آنها introspect کرد و آنها را ترجمه کرد (برای مثال به یک statement از SQL).
C# 3.0 همچنین automatic propertyها و partial methodها را اضافه کرد.
automatic propertyها (فصل 3) با واگذاری کارِ نوشتن propertyهایی که صرفاً یک backing field خصوصی را get/set میکنند به کامپایلر، مقدار کار لازم را کاهش میدهند.
partial methodها (فصل 3) به یک partial class تولیدشدهٔ خودکار اجازه میدهند hookهای قابل سفارشیسازی برای کدنویسی دستی فراهم کند که اگر استفاده نشوند «آب میشوند» و از میان میروند.
قابلیتهای جدید C# 2.0
قابلیتهای بزرگ جدید در C# 2 عبارت بودند از genericها (فصل 3)، value typeهای nullable (فصل 4)، iteratorها (فصل 4) و anonymous methodها (پیشدرآمد عبارتهای لامبدا). این قابلیتها راه را برای معرفی LINQ در C# 3 هموار کردند.
C# 2 همچنین پشتیبانی از partial classها، static classها و مجموعهای از قابلیتهای کوچک و متنوع مانند namespace alias qualifier، friend assemblyها و bufferهای با اندازهٔ ثابت را اضافه کرد.
معرفی genericها به CLR جدیدی (CLR 2.0) نیاز داشت، زیرا genericها در زمان اجرا وفاداری کامل type را حفظ میکنند.