C#: طريقة متقدمة

1. تحميل الدالة

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

▶ مثال

CSHARP
using System;

class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }

    public double Add(double a, double b)
    {
        return a + b;
    }

    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }
}

class Program
{
    static void Main()
    {
        Calculator calc = new Calculator();
        Console.WriteLine(calc.Add(1, 2));
        Console.WriteLine(calc.Add(1.5, 2.5));
        Console.WriteLine(calc.Add(1, 2, 3));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
3
4
6
💡 نصيحة: لا يُعد اختلاف نوع القيمة المرجعة وحده بمثابة تحميل زائد — فالمُترجم سيُبلغ عن وجود خطأ.


2. المعلمات الاختيارية والمعلمة ذات الاسم

تُعيّن المعلمات الاختيارية قيمًا افتراضية للمعلمات في إعلان الدالة؛ ويمكن حذف هذه المعلمات عند استدعاء الدالة. أما المعلمات المسماة، فتنقل القيم عن طريق تحديد أسماء المعلمات، دون الحاجة إلى اتباع الترتيب الموضعي.

▶ مثال

CSHARP
using System;

class Program
{
    static void Print(string msg, int count = 1, string prefix = "")
    {
        for (int i = 0; i < count; i++)
        {
            Console.WriteLine($"{prefix}{msg}");
        }
    }

    static void Main()
    {
        Print("Hello");
        Print("Hi", 3);
        Print(msg: "World", prefix: "> ");
        Print(prefix: ">> ", count: 2, msg: "Test");
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Hello
Hi
Hi
Hi
> World
>> Test
>> Test
⚠️ ملاحظة: يجب وضع المعلمات الاختيارية بعد جميع المعلمات الإلزامية.


3. مُعدِّل المعلمة «ref»

تقوم الكلمة الرئيسية ref بتمرير معلمة بالرجوع — أي أن التعديلات التي تُجرى على المعلمة داخل الدالة تؤثر على متغير الدافع. يجب تهيئة المتغير قبل الاستدعاء، كما يجب استخدام الكلمة الرئيسية ref في موقع الاستدعاء.

▶ مثال

CSHARP
using System;

class Program
{
    static void Swap(ref int a, ref int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }

    static void Main()
    {
        int x = 10;
        int y = 20;
        Console.WriteLine($"Before: x={x}, y={y}");
        Swap(ref x, ref y);
        Console.WriteLine($"After: x={x}, y={y}");
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Before: x=10, y=20
After: x=20, y=10

4. مُعدِّل المعلمة «out»

يتم تمرير الكلمة الرئيسية out أيضًا بالرجوع، ولكن يجب أن تقوم الدالة بتعيين قيمة للمعلمة out، ولا يلزم تهيئة المتغير قبل الاستدعاء. ويُستخدم هذا الأسلوب عادةً عندما تحتاج الدالة إلى إرجاع قيم متعددة.

▶ مثال

CSHARP
using System;

class Program
{
    static bool TryParseInt(string s, out int result)
    {
        result = 0;
        foreach (char c in s)
        {
            if (c < '0' || c > '9')
                return false;
            result = result * 10 + (c - '0');
        }
        return true;
    }

    static void Main()
    {
        if (TryParseInt("123", out int number))
        {
            Console.WriteLine($"Parsed: {number}");
        }

        if (!TryParseInt("abc", out int fail))
        {
            Console.WriteLine("Parse failed");
        }
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Parsed: 123
Parse failed
📌 نقطة أساسية: يجب تعيين قيم المعلمات out قبل أن تُرجع الدالة قيمتها. بدءًا من الإصدار C# 7، يمكنك إعلان المتغيرات مباشرةً في موقع الاستدعاء.


5. مُعدِّل المعلمة «in»

تقوم الكلمة الرئيسية in بتمرير معلمة بالرجوع إليها على أنها للقراءة فقط — ولا يمكن للطريقة تعديل قيمة معلمة in. ويُعد ذلك مفيدًا لتمرير الهياكل الكبيرة لتجنب عبء النسخ مع منع التعديل غير المقصود.

▶ مثال

CSHARP
using System;

struct BigData
{
    public long A, B, C, D, E, F, G, H;
}

class Program
{
    static void Process(in BigData data)
    {
        Console.WriteLine($"A={data.A}, B={data.B}");
    }

    static void Main()
    {
        BigData d = new BigData { A = 100, B = 200 };
        Process(in d);
        Process(d);
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
A=100, B=200
A=100, B=200

ℹ️ ملاحظة: يمكن حذف الكلمة الرئيسية in في موقع الاستدعاء؛ حيث سيقوم المُجمِّع تلقائيًّا بتمريرها بالمرجع.


6. المعلمات المتغيرة

تسمح الكلمة الرئيسية params لأي دالة بقبول عدد متغير من الوسيطات — حيث يقوم المُجمِّع بتجميعها في صفيف. ولا يمكن أن تحتوي الدالة إلا على معلمة واحدة من نوع params، ويجب أن تكون هذه المعلمة هي الأخيرة في قائمة المعلمات.

▶ مثال

CSHARP
using System;

class Program
{
    static int Sum(params int[] numbers)
    {
        int total = 0;
        foreach (int n in numbers)
        {
            total += n;
        }
        return total;
    }

    static void Main()
    {
        Console.WriteLine(Sum(1, 2, 3));
        Console.WriteLine(Sum(10, 20, 30, 40, 50));
        Console.WriteLine(Sum());
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
6
150
0
⚠️ ملاحظة: لا يمكن استخدام params إلا مع المصفوفات أحادية البعد، ولا يمكن أن تحتوي أي دالة على أكثر من معلمة params واحدة.


7. مبادئ التكرار وأمثلة كلاسيكية

التكرار هو تقنية برمجية تقوم فيها إحدى الطرق باستدعاء نفسها. يجب أن يكون لكل عملية تكرار شرط إنهاء (الحالة الأساسية)؛ وإلا فإنها ستؤدي إلى تجاوز سعة المكدس.

(1) المضاعف

CSHARP
using System;

class Program
{
    static long Factorial(int n)
    {
        if (n <= 1)
            return 1;
        return n * Factorial(n - 1);
    }

    static void Main()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"{i}! = {Factorial(i)}");
        }
    }
}
TEXT 📖 للعرض فقط
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800

(2) متتابعة فيبوناتشي

CSHARP
using System;

class Program
{
    static int Fibonacci(int n)
    {
        if (n <= 0) return 0;
        if (n == 1) return 1;
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }

    static void Main()
    {
        for (int i = 0; i <= 10; i++)
        {
            Console.Write($"{Fibonacci(i)} ");
        }
        Console.WriteLine();
    }
}
TEXT 📖 للعرض فقط
0 1 1 2 3 5 8 13 21 34 55
🔥 تحذير بشأن الأداء: تتطلب الطريقة التكرارية البسيطة لتسلسل فيبوناتشي الكثير من العمليات الحسابية الزائدة عن الحاجة، مع تعقيد زمني يبلغ O(2^n). في بيئة الإنتاج، استخدم التكرار أو التخزين المؤقت لتحسين الأداء.


8. الدوال المحلية

أدخلت لغة C# 7 مفهوم الدوال المحلية، مما يسمح بتعريف الدوال داخل دوال أخرى. ولا يمكن الوصول إلى الدوال المحلية إلا من داخل الدالة التي تحتوي عليها، وهي مفيدة لاستخراج المنطق المساعد الذي يُستخدم فقط في دالة معينة.

▶ مثال

CSHARP
using System;

class Program
{
    static int[] GetEvenSquares(int[] numbers)
    {
        bool IsEven(int n) => n % 2 == 0;
        int Square(int n) => n * n;

        System.Collections.Generic.List```<int>``` result = new System.Collections.Generic.List```<int>```();
        foreach (int n in numbers)
        {
            if (IsEven(n))
            {
                result.Add(Square(n));
            }
        }
        return result.ToArray();
    }

    static void Main()
    {
        int[] data = { 1, 2, 3, 4, 5, 6, 7, 8 };
        int[] result = GetEvenSquares(data);
        Console.WriteLine(string.Join(", ", result));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
4, 16, 36, 64
💡 نصيحة: يمكن للدوال المحلية التقاط المتغيرات المحلية من الدالة التي تحتوي عليها، كما أن عبءها التشغيلي أقل من عبء تعبيرات لامدا.


9. نطاق المتغيرات

يحدد نطاق المتغير الأماكن التي يمكن الوصول إليه فيها. في لغة C#، هناك ثلاثة مستويات رئيسية للنطاق: نطاق الكتلة (داخل كتل if/for/while)، ونطاق الدالة (المتغيرات المحلية المُعرَّفة داخل دالة)، ونطاق الفئة (الحقول المُعرَّفة في فئة).

▶ مثال

CSHARP
using System;

class ScopeDemo
{
    int classField = 100;

    void MethodA()
    {
        int localVar = 10;
        Console.WriteLine($"classField={classField}, localVar={localVar}");

        if (true)
        {
            int blockVar = 20;
            Console.WriteLine($"blockVar={blockVar}");
        }
    }

    void MethodB()
    {
        Console.WriteLine($"classField={classField}");
    }
}

class Program
{
    static void Main()
    {
        ScopeDemo demo = new ScopeDemo();
        demo.MethodA();
        demo.MethodB();
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
classField=100, localVar=10
blockVar=20
classField=100

القاعدة: لا يمكنك تعريف متغيرات محلية تحمل الاسم نفسه داخل نفس الدالة. يمكن للكتل البرمجية الداخلية الوصول إلى المتغيرات الخارجية، لكن الكتل الخارجية لا يمكنها الوصول إلى متغيرات الكتل الداخلية.


❓ أسئلة شائعة

س هل يمكن أن يعتمد تحميل الدالة على نوع الإرجاع وحده؟
ج لا، فالمُترجم يطابق الدوال بناءً على الوسيطات الموجودة في موقع الاستدعاء؛ ونوع الإرجاع ليس جزءًا من التوقيع.
س ما الفرق الأساسي بين ref و out؟
ج تتطلب ref التهيئة قبل الاستدعاء؛ بينما تتطلب out التعيين داخل الدالة. تمرر ref قيمة موجودة بالفعل يمكن تعديلها؛ أما out فتُستخدم عادةً لإرجاع نتائج إضافية.
س هل يمكن أن تكون قيم معلمة params هي null؟
ج نعم، فـ params هي في الأساس مصفوفة؛ ولا يؤدي تمرير القيمة null إلى إنشاء مصفوفة فارغة تلقائيًا، لذا يلزم التحقق من وجود القيمة null داخل الدالة.
س هل التكرار التراجعي أبطأ دائمًا من التكرار العادي؟
ج ليس بالضرورة، لكن التكرار التراجعي يخضع لقيود عمق المكدس ويترتب عليه عبء إضافي؛ وقد يؤدي التكرار التراجعي العميق إلى حدوث استثناء StackOverflowException.
س ما الفرق بين الدوال المحلية وتعبيرات لامدا؟
ج الدوال المحلية هي دالات حقيقية لا تنطوي على أي عبء إضافي متعلق بالمندوبين، وتدعم الأنواع العامة وref/out؛ أما تعبيرات لامدا فتنشئ مندوبين أو أشجار تعبيرات مع تخصيصات إضافية للذاكرة.

📖 ملخص

📝 تمارين

  1. اكتب ثلاث طرق مُثبَّلة لـ Multiply تقبل معلمتين من نوع int، ومعلمتين من نوع double، وثلاث معلمات من نوع int على التوالي، وتُرجع الناتج.
  2. اكتب دالة void Log(string msg, string level = "INFO", bool timestamp = true) واستدعها ثلاث مرات على الأقل باستخدام معلمات مسماة بترتيبات مختلفة.
  3. اكتب دالة void Increment(ref int value) تزيد العدد الصحيح المُمرَّر إليها بمقدار 1، وتحقق في الدالة Main من أن المتغير قد تم تعديله بالفعل.
  4. اكتب دالة bool TryDivide(int a, int b, out double result) تُرجع القيمة «false» عندما تكون قيمة b تساوي 0، وإلا تُرجع نتيجة القسمة عبر المتغير out.
  5. اكتب دالة double Average(params double[] values) تحسب متوسط جميع المعطيات، مع معالجة حالة المصفوفة الفارغة.
  6. قم بتنفيذ طريقة تكرارية لحساب 1+2+3+...+n وقارن النتيجة مع النسخة التكرارية.
  7. اكتب دالة تستخدم داخليًّا دالة محلية لتحديد ما إذا كان عدد ما عددًا أوليًّا، وتُرجع جميع الأعداد الأولية من 1 إلى 50.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%