0Pricing
C# Academy · درس

عدم قابلية التغيير باستخدام init وwith

استخدموا أدوات الضبط التي تعمل مع init فقط لإنشاء كائنات غير قابلة للتغيير، واستخدموا تعبيرات with لإنتاج نسخ معدّلة دون تغيير الأصل.

عدم قابلية التغيير باستخدام init وwith درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.

ما عدم قابلية التغيير؟

الكائن غير القابل للتغيير هو كائن لا يمكن تغييره بعد إنشائه. يمنع عدم قابلية التغيير التعديل غير المقصود، ويجعل الكائنات آمنة للخيوط افتراضيًا، ويبسط فهم التعليمات البرمجية. يوفر C# مُعيّنات init وتعبيرات with لجعل عدم قابلية التغيير سهل الاستخدام.

مُعيّنات init فقط

يتيح موصّل init تعيين الخاصية أثناء تهيئة الكائن (في المُنشئ أو مهيئ الكائن)، ولكن ليس بعد ذلك. وهو النظير غير القابل للتغيير لـ set.

public class Point
{
    public double X { get; init; }
    public double Y { get; init; }
}

var p = new Point { X = 3.0, Y = 4.0 }; // OK — init phase
p.X = 5.0; // COMPILE ERROR — cannot set after init

// All of these are valid init-phase assignments:
var p2 = new Point(X: 1.0, Y: 2.0);
var p3 = new Point { X = 0, Y = 0 };

init في خصائص السجل الموضعية

تكون خصائص السجل الموضعية مخصصة لـ init فقط افتراضيًا، وهذا ما يجعل السجلات غير قابلة للتغيير. ينشئ المترجم get; init; لكل معلمة موضعية.

// This record:
public record Person(string Name, int Age);

// Is equivalent to:
public record Person
{
    public string Name { get; init; }
    public int Age    { get; init; }
    public Person(string Name, int Age) { this.Name = Name; this.Age = Age; }
    // + Deconstruct, Equals, GetHashCode, ToString
}

var alice = new Person("Alice", 30);
alice.Name = "Bob"; // COMPILE ERROR

تعبيرات with: التغيير غير الهدّام

ينشئ تعبير with نسخة من سجل مع تغيير خصائص محددة. ويبقى السجل الأصلي دون تغيير، مما يحقق عدم قابلية التغيير فعليًا.

public record Person(string Name, int Age, string Email);

var alice = new Person("Alice", 30, "alice@example.com");

// Create a modified copy — alice is unchanged
var olderAlice = alice with { Age = 31 };
var renamed    = alice with { Name = "Alicia", Email = "alicia@example.com" };

Console.WriteLine(alice.Name);      // Alice (unchanged)
Console.WriteLine(olderAlice.Age);  // 31
Console.WriteLine(renamed.Name);    // Alicia

with مع الأنواع غير السجلية

يتيح C# 10 استخدام with مع أي بنية أو فئة تتوفر فيها دلالات مُنشئ النسخ، لكنه يكون أكثر ملاءمة مع السجلات. أما مع الفئات، فعليك تنفيذه يدويًا.

// struct with with-expression:
public struct Temperature
{
    public double Celsius { get; init; }
    public double Fahrenheit => Celsius * 9 / 5 + 32;
}

var t1 = new Temperature { Celsius = 20 };
var t2 = t1 with { Celsius = 25 }; // copy with change
Console.WriteLine(t1.Celsius); // 20 — unchanged
Console.WriteLine(t2.Celsius); // 25

تسلسل تعبيرات with

سلسِل عدة تعبيرات with لبناء تحويلات معقدة تدريجيًا، بحيث تنشئ كل خطوة قيمة غير قابلة للتغيير انطلاقًا من الخطوة السابقة.

public record Order(int Id, string Status, decimal Total, DateTime UpdatedAt);

var order = new Order(42, "Pending", 99.99m, DateTime.UtcNow);

// Apply a promotion discount then mark as confirmed
var finalOrder = order
    with { Total    = order.Total * 0.9m }    // 10% off
    with { Status   = "Confirmed" }
    with { UpdatedAt = DateTime.UtcNow };

Console.WriteLine(order.Status);     // Pending (original unchanged)
Console.WriteLine(finalOrder.Status); // Confirmed

الخصائص المحسوبة في الأنواع غير القابلة للتغيير

تكون الخصائص المشتقة في الأنواع غير القابلة للتغيير نقية بطبيعتها؛ فهي تُحسب من قيم الخصائص الثابتة، وتعيد النتيجة نفسها دائمًا للمدخل نفسه.

public record Money(decimal Amount, string Currency)
{
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException("Currency mismatch");
        return this with { Amount = Amount + other.Amount };
    }

    public Money Multiply(decimal factor) =>
        this with { Amount = Amount * factor };

    public override string ToString() =>
        $"{Amount:F2} {Currency}";
}

var price = new Money(10.00m, "USD");
var tax   = price.Multiply(0.08m);
var total = price.Add(tax);
Console.WriteLine(total); // 10.80 USD

المجموعات غير القابلة للتغيير

اجمع السجلات مع ImmutableList<T> والأنواع الأخرى من System.Collections.Immutable لإنشاء هياكل بيانات غير قابلة للتغيير بالكامل.

using System.Collections.Immutable;

public record ShoppingCart(
    string UserId,
    ImmutableList<CartItem> Items)
{
    public ShoppingCart AddItem(CartItem item) =>
        this with { Items = Items.Add(item) };

    public ShoppingCart RemoveItem(int productId) =>
        this with { Items = Items.RemoveAll(i => i.ProductId == productId) };

    public decimal Total => Items.Sum(i => i.Price * i.Quantity);
}

أمان الخيوط الناتج عن عدم قابلية التغيير

تكون الكائنات غير القابلة للتغيير آمنة للخيوط بطبيعتها؛ فلا حاجة إلى المزامنة عند مشاركتها بين الخيوط، لأن حالتها لا يمكن أن تتغير بعد إنشائها.

// Immutable config record shared across all threads safely
public record AppConfig(
    string ConnectionString,
    int MaxRetries,
    TimeSpan Timeout);

// Register as singleton — safe because record is immutable
builder.Services.AddSingleton(
    new AppConfig(
        ConnectionString: config["DB"]!,
        MaxRetries: 3,
        Timeout: TimeSpan.FromSeconds(30)));

// Any thread can read this simultaneously without locks

من الواقع العملي: Event Sourcing الوظيفي

تنسجم السجلات غير القابلة للتغيير طبيعيًا مع Event Sourcing: فكل حدث في المجال غير قابل للتغيير، وتُشتق الحالة من خلال طيّ الأحداث، من دون تعديل أو مفاجآت.

public record OrderState(
    int Id,
    string Status = "Draft",
    decimal Total = 0m);

public static OrderState Apply(OrderState state, object evt) => evt switch
{
    OrderPlaced   e => state with { Status = "Pending", Total = e.Total },
    OrderShipped  _ => state with { Status = "Shipped" },
    OrderCancelled _ => state with { Status = "Cancelled" },
    _ => state
};

// Fold events to get current state:
var state = events.Aggregate(
    new OrderState(id),
    Apply);

اختبار سريع

ماذا يفعل تعبير 'with' بالسجل الأصلي؟

مراجعة: عدم قابلية التغيير باستخدام init وwith

أهم النقاط:

  • موصّل init: يمكن تعيينه أثناء التهيئة فقط، وليس بعدها
  • خصائص السجل الموضعية مخصصة لـ init فقط افتراضيًا
  • تعبير with: ينشئ نسخة معدّلة، بينما يبقى الأصل دون تغيير
  • سلسِل تعبيرات with لتنفيذ تحويلات متعددة الخطوات
  • الأنواع غير القابلة للتغيير آمنة للخيوط من دون مزامنة
  • اجمعها مع ImmutableList<T> لإنشاء مخططات كائنات غير قابلة للتغيير بالكامل

الأسئلة الشائعة

هل درس «عدم قابلية التغيير باستخدام init وwith» مجاني؟

نعم — نص درس «عدم قابلية التغيير باستخدام init وwith» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.

ماذا ستتعلم في «عدم قابلية التغيير باستخدام init وwith»؟

استخدموا أدوات الضبط التي تعمل مع init فقط لإنشاء كائنات غير قابلة للتغيير، واستخدموا تعبيرات with لإنتاج نسخ معدّلة دون تغيير الأصل. تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟

لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «عدم قابلية التغيير باستخدام init وwith»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟

نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أنواع Record: الأساسيات وبناء الجملة
  2. عدم قابلية التغيير باستخدام init وwith
  3. مساواة القيم والتفكيك
  4. Records في التصميم المستند إلى النطاق
← العودة إلى C# Academy