مقدمه

گاهی اوقات در برنامه، به یک شیء ساده و موقتی برای نگهداری مجموعه‌ای از پراپرتی‌های فقط-خواندنی نیاز داریم. برای مثال، زمانی که از یک پایگاه داده، داده‌ها را کوئری می‌زنیم، ممکن است فقط به دو یا سه ستون از یک جدول بزرگ نیاز داشته باشیم. تعریف یک کلاس یا ساختار جدید فقط برای این استفاده‌ی موقت، می‌تواند کاری زمان‌بر و غیرضروری باشد. C# برای این سناریوها یک راهکار بسیار راحت فراهم می‌کند: انواع ناشناس (Anonymous Types). این ویژگی به ما اجازه می‌دهد تا اشیائی را "در لحظه" (on the fly) ایجاد کنیم، بدون اینکه نیاز به تعریف صریح نوع آن‌ها داشته باشیم.

نوع ناشناس چیست؟

یک نوع ناشناس، همانطور که از نامش پیداست، کلاسی است که نام مشخصی ندارد. شما با استفاده از کلمه‌ی کلیدی new و سینتکس مقداردهی اولیه شیء، یک نمونه از آن را می‌سازید و کامپایلر در پشت صحنه یک کلاس با نامی منحصر به فرد (که برای ما قابل دسترس نیست) برای آن تولید می‌کند.

ویژگی‌های کلیدی انواع ناشناس:

  • با استفاده از new { ... } ایجاد می‌شوند.
  • کامپایلر نام و نوع پراپرتی‌ها را از روی مقداردهی اولیه استنباط می‌کند.
  • تمام پراپرتی‌های یک نوع ناشناس به صورت فقط-خواندنی (read-only) هستند.
  • برای نگهداری آن‌ها باید از کلمه‌ی کلیدی var استفاده کرد، زیرا ما نام نوع را برای تعریف متغیر در اختیار نداریم.

ساخت و استفاده از یک نوع ناشناس

ایجاد یک نوع ناشناس بسیار ساده است. شما پراپرتی‌ها و مقادیر مورد نظر خود را درون آکولاد قرار می‌دهید.

Copy Icon Program.cs
// Creating an anonymous type with three properties.
var product = new { Name = "Laptop", Price = 1200.50, InStock = true };

// The compiler creates a class behind the scenes with read-only properties
// of type string, double, and bool.

Console.WriteLine($"Product: {product.Name}, Price: ${product.Price}");

// This would cause a compile-time error because properties are read-only.
// product.Name = "Desktop";

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

Copy Icon Program.cs
string carModel = "Tesla Model 3";
int carYear = 2024;

// The properties will be named 'carModel' and 'carYear'.
var myCar = new { carModel, carYear };

Console.WriteLine($"My car is a {myCar.carYear} {myCar.carModel}.");

کاربرد اصلی: کوئری‌های LINQ

اگرچه انواع ناشناس می‌توانند در موارد مختلفی به کار روند، اما کاربرد اصلی و درخشان آن‌ها در کوئری‌های LINQ است. با استفاده از LINQ، ما اغلب داده‌ها را از یک منبع بزرگ (مانند یک لیست یا پایگاه داده) واکشی کرده و آن‌ها را به یک شکل جدید "پرتاب" (Project) می‌کنیم. انواع ناشناس برای ساختن این شکل جدید و موقتی از داده‌ها، ابزاری ایده‌آل هستند.

فرض کنید لیستی از محصولات داریم و می‌خواهیم فقط نام و قیمت هر محصول را به همراه یک متن تخفیف نمایش دهیم.

Copy Icon Program.cs
public record Product(int Id, string Name, decimal Price);

var products = new List<Product>
{
    new(1, "Laptop", 1200m),
    new(2, "Mouse", 25m),
    new(3, "Keyboard", 75m)
};

// Use an anonymous type in a LINQ 'select' clause to create a projection.
var productDisplayList = from p in products
                             select new 
                             { 
                                 ProductName = p.Name, 
                                 DisplayPrice = $"{p.Price:C}" // Format as currency
                             };

foreach (var item in productDisplayList)
{
    Console.WriteLine($"Item: {item.ProductName}, Price: {item.DisplayPrice}");
}

در این مثال، به جای اینکه کل شیء Product را برگردانیم، با استفاده از select new { ... } یک نوع ناشناس موقتی ساخته‌ایم که فقط شامل داده‌هایی است که برای نمایش نیاز داریم. این کار نه تنها کد را خواناتر می‌کند، بلکه می‌تواند باعث بهبود عملکرد نیز شود، زیرا فقط داده‌های ضروری پردازش و منتقل می‌شوند.

محدودیت‌ها و ملاحظات

  • فقط-خواندنی: تمام پراپرتی‌های یک نوع ناشناس init-only هستند. پس از ایجاد، نمی‌توانید مقدار آن‌ها را تغییر دهید.
  • محدوده‌ی محلی: انواع ناشناس بیشتر برای استفاده در محدوده‌ی یک متد طراحی شده‌اند. از آنجایی که نام نوع آن‌ها برای ما مشخص نیست، نمی‌توانیم به سادگی آن‌ها را به عنوان پارامتر به یک متد دیگر ارسال کرده یا به عنوان مقدار بازگشتی یک متد برگردانیم (هرچند راهکارهایی برای این کار وجود دارد، اما معمولاً نشانه‌ی طراحی نامناسب است). برای این سناریوها، استفاده از تاپل‌ها (Tuples) اغلب انتخاب بهتری است.
  • عدم وجود رفتار: انواع ناشناس نمی‌توانند متد، رویداد یا اعضای پیچیده‌ی دیگری داشته باشند. آن‌ها صرفاً برای نگهداری داده هستند.