پاسخ:
C یک زبان برنامهنویسی رویهای (Procedural) است، در حالی که C# یک زبان شیءگرا (Object-Oriented) است. تفاوت اصلی و مهم این دو زبان در این است که C# از قابلیت جمعآوری خودکار زباله (Automatic Garbage Collection) توسط Common Language Runtime (CLR) پشتیبانی میکند، در حالی که C این قابلیت را ندارد.
C# برای اجرا به فریمورک .NET نیاز دارد، در صورتی که C یک زبان مستقل از پلتفرم است. C# همچنین دارای ویژگیهایی مانند مدیریت حافظه امنتر، پشتیبانی از Generics، LINQ و Async/Await است که در C وجود ندارند.
در C#، نیازی به مدیریت دستی حافظه برای آبجکتها نیست؛ CLR به صورت خودکار حافظه اشغال شده توسط آبجکتهای غیرقابل دسترس را آزاد میکند. در مقابل، در C برنامهنویس باید به صورت دستی حافظه را با توابعی مانند malloc
و free
مدیریت کند.
// C# - Garbage Collection خودکار class MyClass { public MyClass() { Console.WriteLine("MyClass Created"); } ~MyClass() { Console.WriteLine("MyClass Finalized"); } // فراخوانی توسط GC } // در متد Main یا هر جای دیگر MyClass obj = new MyClass(); // پس از اتمام استفاده از obj، CLR به صورت خودکار حافظه را مدیریت میکند. obj = null; // آبجکت برای GC واجد شرایط میشود GC.Collect(); // فراخوانی دستی GC (فقط برای تست، در عمل توصیه نمیشود)
// C - مدیریت دستی حافظه #include <stdio.h> #include <stdlib.h> int main() { int *arr; arr = (int *)malloc(5 * sizeof(int)); // تخصیص حافظه if (arr == NULL) { printf("Memory allocation failed\n"); return 1; } // استفاده از arr free(arr); // آزاد کردن حافظه return 0; }
IEnumerable
و IQueryable
در C# چیست؟ چه زمانی از هر کدام استفاده میکنید؟پاسخ:
IEnumerable
برای پرسوجو در مجموعههای درون حافظه (in-memory collections) استفاده میشود، در حالی که IQueryable
برای پرسوجو در منابع داده خارجی مانند پایگاههای داده کاربرد دارد.
IQueryable
به شما امکان میدهد تا پرسوجوهای پیچیدهتری را در سمت سرور بنویسید و اجرا کنید، زیرا میتواند عبارتهای پرسوجو را به زبان SQL یا سایر زبانهای پرسوجو ترجمه کند و تنها دادههای مورد نیاز را از پایگاه داده واکشی کند. این باعث بهبود عملکرد و کاهش مصرف حافظه میشود.
فرض کنید لیستی از کاربران در حافظه دارید و میخواهید کاربران فعال را فیلتر کنید:
// استفاده از IEnumerable List<User> users = GetUsersFromMemory(); // فرض کنید این لیست از جایی پر شده است IEnumerable<User> activeUsers = users.Where(u => u.IsActive); // فیلتر در حافظه
حال فرض کنید میخواهید کاربران فعال را از یک پایگاه داده واکشی کنید:
// استفاده از IQueryable با Entity Framework Core public IQueryable<User> GetActiveUsersFromDatabase(DbContext context) { // این پرسوجو به SQL ترجمه شده و در پایگاه داده اجرا میشود return context.Users.Where(u => u.IsActive); }
IEnumerable
، تمام کاربران ابتدا از حافظه واکشی میشوند و سپس فیلتر اعمال میشود. اما در مثال IQueryable
، فیلتر Where
به یک عبارت SQL ترجمه میشود و تنها کاربران فعال از پایگاه داده واکشی میشوند، که کارایی بالاتری دارد.
async/await
و Task.Run
در C# هنگام کار با کد ناهمزمان (asynchronous) چیست؟پاسخ:
async/await
برای ایجاد متدهای ناهمزمان استفاده میشود که میتوانند بدون مسدود کردن ترد اصلی (main thread) منتظر بمانند. این برای عملیات I/O-bound (مانند دسترسی به شبکه یا پایگاه داده) که نیازی به مصرف CPU ندارند، بسیار مناسب است.
Task.Run
برای اجرای یک delegate یا عبارت lambda بر روی یک ترد از ThreadPool به صورت ناهمزمان استفاده میشود. این ابزار برای عملیات CPU-bound (مانند محاسبات سنگین) که ممکن است ترد اصلی را مسدود کنند، مفید است.
// استفاده از async/await برای عملیات I/O-bound public async Task<string> DownloadContentAsync(string url) { using (HttpClient client = new HttpClient()) { // این عملیات I/O-bound است و ترد اصلی را مسدود نمیکند string content = await client.GetStringAsync(url); return content; } }
// استفاده از Task.Run برای عملیات CPU-bound public async Task<long> CalculateFactorialAsync(int number) { // این عملیات CPU-bound است و با Task.Run به ترد دیگری منتقل میشود return await Task.Run(() => { long result = 1; for (int i = 1; i <= number; i++) { result *= i; } return result; }); }
DownloadContentAsync
، await
به سیستم اجازه میدهد تا در حین دانلود، ترد اصلی را آزاد کند و به کارهای دیگر بپردازد. در CalculateFactorialAsync
، Task.Run
تضمین میکند که محاسبه سنگین فاکتوریل بر روی یک ترد پسزمینه اجرا شود و ترد اصلی آزاد بماند تا UI پاسخگو باشد.
پاسخ:
دادهها را میتوان با استفاده از ساختارهای دادهای ایمن برای ترد (thread-safe data structures) مانند ConcurrentDictionary
و ConcurrentQueue
یا ساختارهای همگامسازی (synchronization constructs) مانند lock
، Monitor
و Semaphore
بین تسکها به اشتراک گذاشت.
انتخاب روش مناسب بستگی به نوع داده، نیاز به همگامسازی و پیچیدگی سناریو دارد.
۱. استفاده از lock
:
private static readonly object _lock = new object(); private static int _counter = 0; public void IncrementCounter() { lock (_lock) { _counter++; } }
۲. استفاده از ConcurrentBag
(برای مجموعههایی که ترتیب مهم نیست):
using System.Collections.Concurrent; private static ConcurrentBag<int> _data = new ConcurrentBag<int>(); public void AddData(int item) { _data.Add(item); }
۳. استفاده از SemaphoreSlim
(برای محدود کردن دسترسی به منابع):
private static SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); // حداکثر 1 ترد همزمان public async Task AccessResourceAsync() { await _semaphore.WaitAsync(); try { // دسترسی به منبع مشترک Console.WriteLine("Resource accessed"); } finally { _semaphore.Release(); } }
پاسخ:
برای پیادهسازی یک Middleware سفارشی برای مدیریت استثناها در ASP.NET Core، مراحل زیر را دنبال میکنید:
RequestDelegate next
و یک متد InvokeAsync
(یا Invoke
) داشته باشد.InvokeAsync
، از یک بلوک try-catch
برای گرفتن استثناها استفاده کنید. در بلوک catch
، میتوانید استثنا را لاگ کنید و یک پاسخ مناسب (مانند یک صفحه خطا یا JSON با جزئیات خطا) به کلاینت برگردانید.app.UseMiddleware<YourCustomExceptionMiddleware>()
در کلاس Startup
(یا Program.cs
در .NET 6 به بالا) برای افزودن Middleware به pipeline درخواست استفاده کنید.// 1. ایجاد کلاس Middleware public class CustomExceptionMiddleware { private readonly RequestDelegate _next; private readonly ILogger<CustomExceptionMiddleware> _logger; public CustomExceptionMiddleware(RequestDelegate next, ILogger<CustomExceptionMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext httpContext) { try { await _next(httpContext); } catch (Exception ex) { _logger.LogError($"Something went wrong: {ex}"); httpContext.Response.ContentType = "application/json"; httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; await httpContext.Response.WriteAsync(new ErrorDetails() { StatusCode = httpContext.Response.StatusCode, Message = "Internal Server Error from the custom middleware." }.ToString()); } } }
// کلاس کمکی برای جزئیات خطا public class ErrorDetails { public int StatusCode { get; set; } public string Message { get; set; } public override string ToString() { return JsonSerializer.Serialize(this); } }
پاسخ:
برای پیادهسازی یک Attribute سفارشی در C#، یک کلاس ایجاد میکنید که از کلاس پایه Attribute
مشتق شده باشد. میتوانید ویژگیها (properties) یا فیلدهایی را برای ذخیره دادههای مرتبط با Attribute اضافه کنید.
برای استفاده از Attribute، آن را با استفاده از سینتکس براکت مربعی ([]
) به کلاسها یا اعضا اعمال میکنید.
فرض کنید میخواهید یک Attribute برای نشانهگذاری کلاسهایی ایجاد کنید که نیاز به لاگبرداری خاصی دارند:
using System; // 1. پیادهسازی Attribute سفارشی [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] public class LoggableAttribute : Attribute { public string Category { get; set; } public LoggableAttribute(string category = "General") { Category = category; } }
// 2. استفاده از Attribute برای تزئین یک کلاس [Loggable(Category = "DataAccess")] public class UserRepository { public void GetUserById(int id) { Console.WriteLine($"Getting user with ID: {id}"); } [Loggable(Category = "Performance")] public void SaveUser(User user) { Console.WriteLine($"Saving user: {user.Name}"); } }
// مثالی از نحوه خواندن Attribute در زمان اجرا (Reflection) public class AttributeReader { public static void ReadAttributes() { Type type = typeof(UserRepository); LoggableAttribute classAttribute = (LoggableAttribute)Attribute.GetCustomAttribute(type, typeof(LoggableAttribute)); if (classAttribute != null) { Console.WriteLine($"UserRepository is Loggable. Category: {classAttribute.Category}"); } // خواندن Attribute از متد var methodInfo = type.GetMethod("SaveUser"); LoggableAttribute methodAttribute = (LoggableAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(LoggableAttribute)); if (methodAttribute != null) { Console.WriteLine($"SaveUser method is Loggable. Category: {methodAttribute.Category}"); } } }
پاسخ:
Covariance و Contravariance مفاهیمی در C# هستند که به شما امکان میدهند تا سازگاری نوع (type compatibility) را در زمان کامپایل برای Delegateها و Interfaceها انعطافپذیرتر کنید. این مفاهیم به شما اجازه میدهند تا از انواع مشتق شده (derived types) یا انواع پایه (base types) در جاهایی که انتظار میرود، استفاده کنید.
out
) کاربرد دارد.in
) کاربرد دارد.// Covariance در Interface public interface IReadOnlyList<out T> // 'out' نشاندهنده Covariance است { T GetItem(int index); } public class Animal { } public class Dog : Animal { } public class DogList : IReadOnlyList<Dog> { private Dog[] dogs = { new Dog(), new Dog() }; public Dog GetItem(int index) => dogs[index]; } public static void TestCovariance() { IReadOnlyList<Dog> dogList = new DogList(); IReadOnlyList<Animal> animalList = dogList; // Covariance: IReadOnlyList<Dog> به IReadOnlyList<Animal> قابل انتساب است Animal animal = animalList.GetItem(0); Console.WriteLine("Covariance Test Passed"); }
// Contravariance در Delegate public delegate void Action<in T>(T arg); // 'in' نشاندهنده Contravariance است public class Animal { } public class Dog : Animal { } public static void ProcessAnimal(Animal animal) { Console.WriteLine($"Processing Animal: {animal.GetType().Name}"); } public static void TestContravariance() { Action<Animal> animalAction = ProcessAnimal; Action<Dog> dogAction = animalAction; // Contravariance: Action<Animal> به Action<Dog> قابل انتساب است dogAction(new Dog()); Console.WriteLine("Contravariance Test Passed"); }
پاسخ:
Serialization فرآیند تبدیل یک شیء (Object) به یک جریان از بایتها (stream of bytes) است تا بتوان آن را ذخیره کرد (مثلاً در یک فایل) یا از طریق شبکه منتقل کرد. این فرآیند امکان حفظ وضعیت یک شیء را فراهم میکند تا بتوان آن را بعداً بازسازی کرد (Deserialization).
Serialization برای حفظ نسخه اصلی کد و بازیابی آن بعداً مفید است.
فرض کنید یک کلاس Person
دارید و میخواهید یک شیء از این کلاس را در یک فایل ذخیره کنید:
using System; using System.IO; using System.Runtime.Serialization.Formatters.Binary; [Serializable] public class Person { public string Name { get; set; } public int Age { get; set; } }
public class SerializationExample { public static void SerializePerson(Person person, string filePath) { using (FileStream stream = new FileStream(filePath, FileMode.Create)) { BinaryFormatter formatter = new BinaryFormatter(); formatter.Serialize(stream, person); Console.WriteLine($"Person object serialized to {filePath}"); } } public static Person DeserializePerson(string filePath) { using (FileStream stream = new FileStream(filePath, FileMode.Open)) { BinaryFormatter formatter = new BinaryFormatter(); Person person = (Person)formatter.Deserialize(stream); Console.WriteLine($"Person object deserialized from {filePath}"); return person; } } }
// نحوه استفاده: // Person p1 = new Person { Name = "Ali", Age = 30 }; // SerializationExample.SerializePerson(p1, "person.dat"); // Person p2 = SerializationExample.DeserializePerson("person.dat"); // Console.WriteLine($"Deserialized Person: {p2.Name}, {p2.Age}");
Continue
و Break
در C# چیست؟پاسخ:
Continue
: این دستور باعث میشود که تکرار فعلی حلقه متوقف شود و حلقه به تکرار بعدی برود. به عبارت دیگر، کدهای باقیمانده در بدنه حلقه نادیده گرفته میشوند و حلقه از ابتدای تکرار بعدی ادامه مییابد.Break
: این دستور باعث میشود که حلقه کاملاً متوقف شود و کنترل برنامه به خط بعد از حلقه منتقل شود. تمام تکرارهای باقیمانده حلقه نادیده گرفته میشوند.// مثال Continue for (int i = 1; i <= 10; i++) { if (i % 2 == 0) // اگر عدد زوج باشد { continue; // به تکرار بعدی برو } Console.WriteLine(i); // فقط اعداد فرد چاپ میشوند } // خروجی: 1, 3, 5, 7, 9
// مثال Break for (int i = 1; i <= 10; i++) { if (i == 5) { break; // حلقه را کاملاً متوقف کن } Console.WriteLine(i); } // خروجی: 1, 2, 3, 4
Continue
فقط تکرار فعلی را رد میکند و به تکرار بعدی میرود، در حالی که Break
کل حلقه را متوقف میکند.
String
و StringBuilder
در C# چیست؟پاسخ:
String
در C# یک نوع داده غیرقابل تغییر (Immutable) است، به این معنی که هر بار که عملیاتی روی یک رشته انجام میدهید، یک شیء جدید در حافظه ایجاد میشود. این موضوع در صورت انجام عملیات متعدد روی رشتهها میتواند باعث مصرف زیاد حافظه و کاهش عملکرد شود.
StringBuilder
یک کلاس قابل تغییر (Mutable) است که برای ساخت و دستکاری رشتهها بهینه شده است. این کلاس از یک بافر داخلی استفاده میکند که میتواند بدون ایجاد اشیاء جدید، محتوای رشته را تغییر دهد.
// استفاده از String (ناکارآمد برای عملیات متعدد) string result = ""; for (int i = 0; i < 1000; i++) { result += "Hello "; // هر بار یک شیء جدید ایجاد میشود } // این کد 1000 شیء String در حافظه ایجاد میکند
// استفاده از StringBuilder (کارآمد برای عملیات متعدد) StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.Append("Hello "); // فقط محتوای بافر داخلی تغییر میکند } string result = sb.ToString(); // تبدیل نهایی به String
String
برای عملیات ساده و محدود استفاده کنید. از StringBuilder
زمانی استفاده کنید که نیاز به انجام عملیات متعدد روی رشتهها دارید، مخصوصاً در حلقهها یا عملیات پیچیده.