C#: طريقة متقدمة
1. تحميل الدالة
يتيح تحميل الطرق تعريف عدة طرق تحمل الاسم نفسه في نفس الفئة، شريطة أن تختلف قوائم معلماتها. ويُقصد باختلاف قائمة المعلمات اختلاف عدد المعلمات أو نوعها أو ترتيبها. ولا يؤثر نوع القيمة المرجعة في عملية تحديد الطريقة المُحمَّلة.
▶ مثال
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));
}
}
3
4
6
2. المعلمات الاختيارية والمعلمة ذات الاسم
تُعيّن المعلمات الاختيارية قيمًا افتراضية للمعلمات في إعلان الدالة؛ ويمكن حذف هذه المعلمات عند استدعاء الدالة. أما المعلمات المسماة، فتنقل القيم عن طريق تحديد أسماء المعلمات، دون الحاجة إلى اتباع الترتيب الموضعي.
▶ مثال
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");
}
}
Hello
Hi
Hi
Hi
> World
>> Test
>> Test
3. مُعدِّل المعلمة «ref»
تقوم الكلمة الرئيسية ref بتمرير معلمة بالرجوع — أي أن التعديلات التي تُجرى على المعلمة داخل الدالة تؤثر على متغير الدافع. يجب تهيئة المتغير قبل الاستدعاء، كما يجب استخدام الكلمة الرئيسية ref في موقع الاستدعاء.
▶ مثال
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}");
}
}
Before: x=10, y=20
After: x=20, y=10
4. مُعدِّل المعلمة «out»
يتم تمرير الكلمة الرئيسية out أيضًا بالرجوع، ولكن يجب أن تقوم الدالة بتعيين قيمة للمعلمة out، ولا يلزم تهيئة المتغير قبل الاستدعاء. ويُستخدم هذا الأسلوب عادةً عندما تحتاج الدالة إلى إرجاع قيم متعددة.
▶ مثال
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");
}
}
}
Parsed: 123
Parse failed
out قبل أن تُرجع الدالة قيمتها. بدءًا من الإصدار C# 7، يمكنك إعلان المتغيرات مباشرةً في موقع الاستدعاء.
5. مُعدِّل المعلمة «in»
تقوم الكلمة الرئيسية in بتمرير معلمة بالرجوع إليها على أنها للقراءة فقط — ولا يمكن للطريقة تعديل قيمة معلمة in. ويُعد ذلك مفيدًا لتمرير الهياكل الكبيرة لتجنب عبء النسخ مع منع التعديل غير المقصود.
▶ مثال
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);
}
}
A=100, B=200
A=100, B=200
ℹ️ ملاحظة: يمكن حذف الكلمة الرئيسية
inفي موقع الاستدعاء؛ حيث سيقوم المُجمِّع تلقائيًّا بتمريرها بالمرجع.
6. المعلمات المتغيرة
تسمح الكلمة الرئيسية params لأي دالة بقبول عدد متغير من الوسيطات — حيث يقوم المُجمِّع بتجميعها في صفيف. ولا يمكن أن تحتوي الدالة إلا على معلمة واحدة من نوع params، ويجب أن تكون هذه المعلمة هي الأخيرة في قائمة المعلمات.
▶ مثال
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());
}
}
6
150
0
params إلا مع المصفوفات أحادية البعد، ولا يمكن أن تحتوي أي دالة على أكثر من معلمة params واحدة.
7. مبادئ التكرار وأمثلة كلاسيكية
التكرار هو تقنية برمجية تقوم فيها إحدى الطرق باستدعاء نفسها. يجب أن يكون لكل عملية تكرار شرط إنهاء (الحالة الأساسية)؛ وإلا فإنها ستؤدي إلى تجاوز سعة المكدس.
(1) المضاعف
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)}");
}
}
}
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
(2) متتابعة فيبوناتشي
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();
}
}
0 1 1 2 3 5 8 13 21 34 55
8. الدوال المحلية
أدخلت لغة C# 7 مفهوم الدوال المحلية، مما يسمح بتعريف الدوال داخل دوال أخرى. ولا يمكن الوصول إلى الدوال المحلية إلا من داخل الدالة التي تحتوي عليها، وهي مفيدة لاستخراج المنطق المساعد الذي يُستخدم فقط في دالة معينة.
▶ مثال
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));
}
}
4, 16, 36, 64
9. نطاق المتغيرات
يحدد نطاق المتغير الأماكن التي يمكن الوصول إليه فيها. في لغة C#، هناك ثلاثة مستويات رئيسية للنطاق: نطاق الكتلة (داخل كتل if/for/while)، ونطاق الدالة (المتغيرات المحلية المُعرَّفة داخل دالة)، ونطاق الفئة (الحقول المُعرَّفة في فئة).
▶ مثال
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();
}
}
classField=100, localVar=10
blockVar=20
classField=100
✅ القاعدة: لا يمكنك تعريف متغيرات محلية تحمل الاسم نفسه داخل نفس الدالة. يمكن للكتل البرمجية الداخلية الوصول إلى المتغيرات الخارجية، لكن الكتل الخارجية لا يمكنها الوصول إلى متغيرات الكتل الداخلية.
❓ أسئلة شائعة
📖 ملخص
- تحميل الطرق: نفس الاسم، معلمات مختلفة؛ ولا يؤثر نوع القيمة المرجعة في ذلك
- المعلمات الاختيارية: تُعيَّن قيمها الافتراضية عند الإعلان عنها، ويجب وضعها بعد المعلمات الإلزامية
- المعلمات المسماة: تمرير القيم حسب اسم المعلمة، مع تجاوز قيود الترتيب
- ref: تمرير بالقيمة المرجعية، يجب تهيئتها قبل الاستدعاء
- out: تمرير بالقيمة المرجعية، يجب التعيين داخل الدالة، ولا يلزم التهيئة قبل الاستدعاء
- في: تمرير بالقراءة فقط عن طريق الإشارة، يمنع التعديل ويتجنب النسخ
- params: عدد متغير من الوسيطات، يتم تجميعها في صيغة مصفوفة، يُسمح بوجود وسيطة واحدة فقط ويجب أن تكون الأخيرة
- التكرار: استدعاء الدالة لنفسها؛ يجب أن يكون لها شرط إنهاء؛ يجب الانتباه إلى الأداء وتجاوز سعة المكدس
- الدوال المحلية: دوال مُعرَّفة داخل دوال أخرى، وهي ميزة متوفرة في C# 7+ وتتميز بتكلفة تشغيل أقل مقارنة بدوال لامدا
- نطاق المتغيرات: الكتلة < الدالة < الفئة؛ يمكن للمتغير الداخلي الوصول إلى المتغير الخارجي، بينما لا يمكن للمتغير الخارجي الوصول إلى المتغير الداخلي
📝 تمارين
- اكتب ثلاث طرق مُثبَّلة لـ
Multiplyتقبل معلمتين من نوع int، ومعلمتين من نوع double، وثلاث معلمات من نوع int على التوالي، وتُرجع الناتج. - اكتب دالة
void Log(string msg, string level = "INFO", bool timestamp = true)واستدعها ثلاث مرات على الأقل باستخدام معلمات مسماة بترتيبات مختلفة. - اكتب دالة
void Increment(ref int value)تزيد العدد الصحيح المُمرَّر إليها بمقدار 1، وتحقق في الدالة Main من أن المتغير قد تم تعديله بالفعل. - اكتب دالة
bool TryDivide(int a, int b, out double result)تُرجع القيمة «false» عندما تكون قيمة b تساوي 0، وإلا تُرجع نتيجة القسمة عبر المتغير out. - اكتب دالة
double Average(params double[] values)تحسب متوسط جميع المعطيات، مع معالجة حالة المصفوفة الفارغة. - قم بتنفيذ طريقة تكرارية لحساب 1+2+3+...+n وقارن النتيجة مع النسخة التكرارية.
- اكتب دالة تستخدم داخليًّا دالة محلية لتحديد ما إذا كان عدد ما عددًا أوليًّا، وتُرجع جميع الأعداد الأولية من 1 إلى 50.