C#: القوائم والهيكلية
1. القوائم (enum)
التعداد (enum) هو نوع قيمة يُستخدم لتعريف مجموعة من الثوابت المسماة، مما يجعل الكود أكثر قابلية للقراءة وأكثر أمانًا من حيث الأنواع.
(1) تعريف قائمة التعداد
استخدم الكلمة الرئيسية enum لتعريف قائمة التعداد. النوع الأساسي الافتراضي هو int، حيث تبدأ القيم من 0 وتتزايد.
▶ مثال
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
class Program
{
static void Main()
{
Season current = Season.Autumn;
System.Console.WriteLine(current);
System.Console.WriteLine((int)current);
}
}
Autumn
2
(2) النوع الأساسي والتعيين الصريح
يُعيَّن نوع «enum» الأساسي افتراضيًّا إلى int. يمكنك تحديد نوع صحيح آخر بشكل صريح وتعيين قيم محددة للعناصر.
▶ مثال
enum Priority : byte
{
Low = 1,
Medium = 2,
High = 3
}
enum HttpStatusCode
{
OK = 200,
NotFound = 404,
InternalServerError = 500
}
class Program
{
static void Main()
{
Priority p = Priority.High;
System.Console.WriteLine((byte)p);
System.Console.WriteLine((int)HttpStatusCode.NotFound);
}
}
3
404
(3) تحويل القيم المُعددّة
يمكن تحويل أنواع التعدادات والأنواع الأساسية المرتبطة بها بشكل صريح من نوع إلى آخر.
▶ مثال
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
class Program
{
static void Main()
{
Season s = Season.Spring;
int value = (int)s;
System.Console.WriteLine(value);
Season fromInt = (Season)2;
System.Console.WriteLine(fromInt);
}
}
0
Autumn
2. طرق فئة Enum
توفر فئة System.Enum عدة طرق ثابتة لإجراء عمليات على القوائم التعدادية.
(1) الطرق الشائعة
| الطريقة | الوصف |
|---|---|
Enum.Parse |
يحول سلسلة إلى قائمة تعدادية؛ ويرمي استثناءً في حالة الفشل |
Enum.TryParse |
يحول سلسلة إلى قيمة من قائمة التعداد؛ ويعيد القيمة «false» في حالة الفشل |
Enum.GetName |
يحصل على اسم قيمة من قائمة التعداد |
Enum.GetValues |
يحصل على جميع قيم التعداد |
Enum.IsDefined |
يحدد ما إذا كانت القيمة موجودة في قائمة التعداد |
▶ مثال
enum Season
{
Spring,
Summer,
Autumn,
Winter
}
class Program
{
static void Main()
{
Season parsed = (Season)Enum.Parse(typeof(Season), "Summer");
System.Console.WriteLine(parsed);
bool ok = Enum.TryParse<Season>("Winter", out Season result);
System.Console.WriteLine($"{ok}, {result}");
string name = Enum.GetName(typeof(Season), 2);
System.Console.WriteLine(name);
foreach (Season s in Enum.GetValues(typeof(Season)))
{
System.Console.WriteLine($"{s} = {(int)s}");
}
System.Console.WriteLine(Enum.IsDefined(typeof(Season), 1));
System.Console.WriteLine(Enum.IsDefined(typeof(Season), 99));
}
}
Summer
True, Winter
Autumn
Spring = 0
Summer = 1
Autumn = 2
Winter = 3
True
False
3. الهياكل (struct)
البنية (struct) هي نوع قيمة مناسب لتغليف كميات صغيرة من البيانات. يتم تخزين البنى في المكدس، وتؤدي عملية التعيين إلى نسخ القيمة بأكملها.
(1) تعريف بنية (Struct)
استخدم الكلمة الرئيسية struct لتعريف بنية.
▶ مثال
struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"({X}, {Y})";
}
}
class Program
{
static void Main()
{
Point p1 = new Point(3, 4);
Point p2 = p1;
p2.X = 10;
System.Console.WriteLine(p1);
System.Console.WriteLine(p2);
}
}
(3, 4)
(10, 4)
4. البنية مقابل الفئة
توجد اختلافات مهمة بين الهياكل (Structs) والفئات (Classes) من حيث الدلالة والسلوك.
(1) المقارنة
| خاصية | بنية | فئة |
|---|---|---|
| فئة النوع | نوع القيمة | نوع المرجع |
| موقع التخزين | المكدس | الركام |
| سلوك التعيين | نسخ القيمة | نسخ المرجع |
| التوريث | غير مدعوم (لا يمكن توريثه) | مدعوم |
| الواجهات | يمكن تنفيذها | يمكن تنفيذها |
| مُدمِّر | لا شيء | نعم |
| القيمة الافتراضية | جميع الحقول بقيمة صفر | null |
▶ مثال
struct PointStruct
{
public int X;
public int Y;
}
class PointClass
{
public int X;
public int Y;
}
class Program
{
static void Main()
{
PointStruct s1 = new PointStruct();
PointStruct s2 = s1;
s2.X = 5;
System.Console.WriteLine($"struct: s1.X={s1.X}, s2.X={s2.X}");
PointClass c1 = new PointClass();
PointClass c2 = c1;
c2.X = 5;
System.Console.WriteLine($"class: c1.X={c1.X}, c2.X={c2.X}");
}
}
struct: s1.X=0, s2.X=5
class: c1.X=5, c2.X=5
5. متى تُستخدم الهياكل (Structs)
لا تُعد البنيات (structs) دائمًا أفضل من الفئات (classes). يُنصح باستخدام البنية في الحالات التالية:
- حجم البيانات صغير (يُوصى بألا يتجاوز 16 بايت)
- يمثل منطقيًّا قيمة واحدة (مثل: الإحداثيات، اللون)
- عمرها قصير، حيث يتم إنشاؤها وإزالتها بشكل متكرر
- لا حاجة إلى التوريث
أمثلة شائعة: Point، Color، DateTime، TimeSpan.
6. البنية readonly (C# 7.2+)
يضمن readonly struct أن تكون جميع الحقول غير قابلة للتعديل. ويمنع المُجمِّع إجراء أي عمليات تعديل.
▶ مثال
readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public double DistanceTo(Point other)
{
int dx = X - other.X;
int dy = Y - other.Y;
return System.Math.Sqrt(dx * dx + dy * dy);
}
}
class Program
{
static void Main()
{
Point p1 = new Point(3, 4);
Point p2 = new Point(0, 0);
System.Console.WriteLine(p1.DistanceTo(p2));
}
}
5
7. بنية «record» (C# 10+)
يجمع record struct بين دلالات القيم الخاصة بالـ«struct» ومساواة القيم والصيغة الموجزة الخاصة بالـ«record»، وهو مناسب للحالات التي تتطلب عدم قابلية التغيير وعناصر يتم إنشاؤها تلقائيًا.
▶ مثال
public class Point : IEquatable<Point>
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public Point With(int? x = null, int? y = null)
{
return new Point(x ?? this.X, y ?? this.Y);
}
public bool Equals(Point other)
{
return other != null && X == other.X && Y == other.Y;
}
public override bool Equals(object obj) => Equals(obj as Point);
public override int GetHashCode() => System.HashCode.Combine(X, Y);
public override string ToString() => $"Point {{ X = {X}, Y = {Y} }}";
}
class Program
{
static void Main()
{
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
System.Console.WriteLine(p1 == p2);
System.Console.WriteLine(p1.With(x: 10));
}
}
True
Point { X = 10, Y = 2 }
record struct بإنشاء تعبيرات Equals وToString وwith وغيرها من العناصر تلقائيًّا، مما يقلل من الكود النمطي.
❓ أسئلة شائعة
set مع حقول البنية القابلة للقراءة فقط؟📖 ملخص
- يتم تعريف القوائم باستخدام
enum، والنوع الأساسي الافتراضي هوint، مع زيادة القيم بدءًا من 0 - يمكن تحديد نوع القيم الأساسية وقيم العناصر بشكل صريح
Enum.Parse/TryParse/GetName/GetValues/IsDefinedتوفير عمليات التعداد- يمكن تحويل أنواع التعداد والأنواع الأساسية بشكل صريح من أحدهما إلى الآخر
- تُعرَّف الهياكل باستخدام
struct، وهي أنواع قيمية، وتُخزَّن في المكدس - التعيين في المتغيرات من نوع «struct» ينسخ القيمة؛ أما التعيين في المتغيرات من نوع «class» فينسخ المرجع
- لا يمكن توريث الهياكل (Structs)، لكن يمكنها تنفيذ الواجهات
- الحالات المناسبة: البيانات الصغيرة (≤16 بايت)، غير قابلة للتعديل، ذات عمر قصير
readonly struct(C# 7.2+) يضمن أن تكون جميع الحقول غير قابلة للتغييرrecord struct(C# 10+) يجمع بين دلالات القيم وبناء الجملة الموجز للسجلات
📝 تمارين
- عرّف
enum Direction { North, East, South, West }، واكتب كودًا للتكرار عبر جميع الاتجاهات وإخراج الاسم والقيمة الصحيحة - عرّف متغيرًا
struct Rectangleبعرض وارتفاع، وقدم طريقة لحساب المساحة، وتأكد من أن عملية التعيين تنسخ القيمة - قم بتغيير
Rectangleمن التمرين السابق إلىreadonly struct، مع التأكد من أن جميع الحقول هي للقراءة فقط وأنها تم تهيئتها من خلال مُنشئ الكائن - استخدم
Enum.TryParseوEnum.IsDefinedلكتابة دالة آمنة لتحليل قيم التعداد (enum)، بحيث تتلقى سلسلة نصية وتُرجع قيمة التعداد المطابقة لها أو القيمة الافتراضية - اكتب كودًا لمقارنة الفرق في السلوك بين
structوclassعند تمريرهما كمعلمات للطريقة (التمرير بالقيمة مقابل التمرير بالمرجع)