C#: المتغيرات وأنواع البيانات
تخيل أن المتغير عبارة عن صندوق عليه ملصق: الملصق هو الاسم، والصندوق يحتوي على البيانات. واختيار الحجم والنوع المناسبين للصندوق يضمن أمان بياناتك وكفاءتها.
1. إعلان المتغيرات وتعيين قيمها الأولية
لغة C# هي لغة ذات أنواع محددة بشكل صارم، لذا يجب عليك تحديد النوع عند إعلان متغير (أو استخدام var للسماح للمترجم باستنتاجه):
int age;
age = 25;
string name = "Alice";
double score = 95.5;
يمكنك أيضًا تعريف متغيرات متعددة من نفس النوع في سطر واحد:
int x = 1, y = 2, z = 3;
تحذير: لا تسمح لغة C# باستخدام المتغيرات المحلية غير المُهيأة؛ حيث سيُصدر المُترجم خطأً.
2. قواعد التسمية
تفرض لغة C# قواعد صارمة لتسمية المتغيرات:
- يجب أن يبدأ بحرف أو بـ
_؛ ولا يجوز أن يبدأ برقم - لا يجوز أن يحتوي إلا على أحرف وأرقام و
_ - يتم التمييز بين الأحرف الكبيرة والصغيرة (
ageوAgeمتغيران مختلفان) - لا يمكن استخدام كلمات C# الرئيسية (مثل
int،class،return)
(1) camelCase و PascalCase
تتضمن لغة C# قاعدتين أساسيتين لتسمية المتغيرات:
| نوع المعرّف | النمط | أمثلة |
|---|---|---|
| المتغيرات المحلية، الحقول | camelCase | studentName، totalCount |
| الطرق، الفئات، الخصائص | PascalCase | CalculateSum، Student |
نصيحة: أسلوب camelCase: الكلمة الأولى بحرف صغير، والكلمات التالية بأحرف كبيرة؛ أسلوب PascalCase: كل كلمة بحرف كبير.
int studentAge = 18;
double averageScore = 87.5;
string homeAddress = "Beijing";
3. أنواع الأعداد الصحيحة
توفر لغة C# 8 أنواع من الأعداد الصحيحة، تشمل الأعداد الموقعة وغير الموقعة على حد سواء:
| النوع | البايتات | النطاق | اللاحقة |
|---|---|---|---|
sbyte |
1 | -128 ~ 127 | — |
byte |
1 | 0 ~ 255 | — |
short |
2 | -32,768 ~ 32,767 | — |
ushort |
2 | 0 ~ 65,535 | — |
int |
4 | -2,147,483,648 ~ 2,147,483,647 | — |
uint |
4 | 0 ~ 4,294,967,295 | U |
long |
8 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | L |
ulong |
8 | 0 ~ 18,446,744,073,709,551,615 | UL |
نصيحة: على عكس لغة Java، تحتوي لغة C# على أنواع أعداد صحيحة غير موقعة (byte، ushort، uint، ulong). لاحظ أن byte في لغة C# غير موقّعة، في حين أن النسخة الموقّعة هي sbyte.
▶ مثال
sbyte sb = -100;
byte b = 200;
short s = -30000;
ushort us = 60000;
int i = 100000;
uint ui = 3000000000U;
long l = 9000000000000000000L;
ulong ul = 18000000000000000000UL;
Console.WriteLine($"sbyte: {sb}");
Console.WriteLine($"byte: {b}");
Console.WriteLine($"short: {s}");
Console.WriteLine($"ushort: {us}");
Console.WriteLine($"int: {i}");
Console.WriteLine($"uint: {ui}");
Console.WriteLine($"long: {l}");
Console.WriteLine($"ulong: {ul}");
sbyte: -100
byte: 200
short: -30000
ushort: 60000
int: 100000
uint: 3000000000
long: 9000000000000000000
ulong: 18000000000000000000
4. أنواع الأرقام العائمة
| النوع | البايتات | الدقة | النطاق (تقريبًا) | اللاحقة | حالة الاستخدام |
|---|---|---|---|---|---|
float |
4 | 7 أرقام | +/-1.5×10⁻⁴⁵ ~ +/-3.4×10³⁸ | f |
الرسومات والألعاب |
double |
8 | 15-16 رقمًا | +/-5.0×10⁻³²⁴ ~ +/-1.7×10³⁰⁸ | d (الافتراضي) |
الحوسبة العلمية |
decimal |
16 | 28-29 رقمًا | +/-1.0×10⁻²⁸ ~ +/-7.9×10²⁸ | m |
المبالغ المالية |
تحذير: القيم العشرية الثابتة تكون double بشكل افتراضي. يجب عليك إضافة لاحقة عند التعيين إلى float أو decimal، وإلا فإن المُترجم سيُصدر خطأً.
float f = 3.14f;
double d = 3.14;
decimal m = 3.14m;
Console.WriteLine($"float: {f}");
Console.WriteLine($"double: {d}");
Console.WriteLine($"decimal: {m}");
float: 3.14
double: 3.14
decimal: 3.14
نصيحة: تم تصميم decimal لإجراء الحسابات المالية وتجنب الأخطاء الثنائية في الأرقام العائمة. على سبيل المثال، 0.1 + 0.2 لا يتطابق تمامًا مع 0.3 عند استخدامه مع double، ولكنه يتطابق تمامًا مع decimal.
5. النوع bool
يحتوي bool على قيمتين فقط: true وfalse، ويشغل 1 بايت.
تحذير: على عكس لغة C، لا يمكن تحويل bool في لغة C# ضمناً إلى أعداد صحيحة أو منها. if(1) غير صحيح؛ يجب عليك كتابة if(true) أو if(x != 0).
bool isReady = true;
bool isEmpty = false;
Console.WriteLine($"isReady: {isReady}");
Console.WriteLine($"isEmpty: {isEmpty}");
isReady: True
isEmpty: False
6. نوع char
يُعد char في لغة C# حرفًا من أحرف يونيكود مكونًا من بايتين، وليس حرفًا من أحرف ASCII مكونًا من بايت واحد كما هو الحال في لغة C.
char ch1 = 'A';
char ch2 = '\u03a9';
Console.WriteLine($"ch1: {ch1}");
Console.WriteLine($"ch2: {ch2}");
Console.WriteLine($"ch1 code: {(int)ch1}");
Console.WriteLine($"ch2 code: {(int)ch2}");
ch1: A
ch2: Ω
ch1 code: 65
ch2 code: 937
نصيحة: يستخدم char علامات الاقتباس المفردة 'A'، بينما يستخدم string علامات الاقتباس المزدوجة "A" — وهما نوعان مختلفان تمامًا.
7. نوع السلسلة
string هو نوع مرجعي يمثل تسلسلًا ثابتًا من أحرف يونيكود.
string greeting = "Hello";
string name = "World";
string message = greeting + ", " + name + "!";
Console.WriteLine(message);
Console.WriteLine($"Length: {message.Length}");
Hello, World!
Length: 13
تحذير: string هو نوع مرجعي، وقيمته الافتراضية هي null (وليس سلسلة فارغة ""). سيؤدي الوصول إلى سلسلة null إلى إثارة استثناء NullReferenceException.
8. var: التعيين الضمني للنوع
قدمت لغة C# 3.0 الكلمة الرئيسية var، التي تتيح للمترجم استنتاج النوع من القيمة الأولية:
var count = 42;
var price = 9.99m;
var label = "hello";
var flag = true;
Console.WriteLine(count.GetType().Name);
Console.WriteLine(price.GetType().Name);
Console.WriteLine(label.GetType().Name);
Console.WriteLine(flag.GetType().Name);
Int32
Decimal
String
Boolean
تحذير: لا تعني var «أي نوع» — حيث يتم تحديد نوع المتغير في وقت التحويل البرمجي ويجب تهيئته. ما يلي غير صحيح:
var x;
var x = null;
نصيحة: استخدم var عندما تكون أسماء الأنواع طويلة أو عندما يكون النوع واضحًا من السياق. تجنب الإفراط في استخدامه، لأنه قد يقلل من سهولة القراءة.
9. sizeof — التحقق من حجم النوع
تُرجع الدالة sizeof عدد البايتات التي يشغلها نوع معين. ويمكن استخدامها مباشرةً مع الأنواع الأولية؛ أما الأنواع الأخرى فتتطلب سياق unsafe.
▶ مثال
Console.WriteLine($"sbyte: {sizeof(sbyte)} bytes");
Console.WriteLine($"byte: {sizeof(byte)} bytes");
Console.WriteLine($"short: {sizeof(short)} bytes");
Console.WriteLine($"ushort: {sizeof(ushort)} bytes");
Console.WriteLine($"int: {sizeof(int)} bytes");
Console.WriteLine($"uint: {sizeof(uint)} bytes");
Console.WriteLine($"long: {sizeof(long)} bytes");
Console.WriteLine($"ulong: {sizeof(ulong)} bytes");
Console.WriteLine($"float: {sizeof(float)} bytes");
Console.WriteLine($"double: {sizeof(double)} bytes");
Console.WriteLine($"decimal: {sizeof(decimal)} bytes");
Console.WriteLine($"char: {sizeof(char)} bytes");
Console.WriteLine($"bool: {sizeof(bool)} bytes");
sbyte: 1 bytes
byte: 1 bytes
short: 2 bytes
ushort: 2 bytes
int: 4 bytes
uint: 4 bytes
long: 8 bytes
ulong: 8 bytes
float: 4 bytes
double: 8 bytes
decimal: 16 bytes
char: 2 bytes
bool: 1 bytes
10. الثوابت (const)
استخدم const لإعلان الثوابت في وقت التحويل البرمجي التي لا يمكن تغيير قيمها. ويجب تهيئة قيمها عند الإعلان:
const double Pi = 3.14159265358979;
const int MaxRetry = 3;
const string AppName = "MyApp";
Console.WriteLine($"Pi = {Pi}");
Console.WriteLine($"MaxRetry = {MaxRetry}");
Console.WriteLine($"AppName = {AppName}");
Pi = 3.14159265358979
MaxRetry = 3
AppName = MyApp
تحذير: عادةً ما تستخدم أسماء const أسلوب كتابة PascalCase. يجب أن تكون القيمة قابلة للتحديد في وقت التحويل البرمجي، ولا يجوز أن تكون متغيرًا أو نتيجة استدعاء دالة.
نصيحة: إذا كنت بحاجة إلى «ثابت» يتم تحديده أثناء وقت التشغيل، فاستخدم حقل readonly بدلاً من ذلك.
11. القيم الافتراضية للأنواع
لكل نوع قيمة افتراضية في حالة عدم تعيين قيمة أولية له:
| النوع | القيمة الافتراضية |
|---|---|
sbyte / byte / short / ushort / int / uint / long / ulong |
0 |
float |
0.0f |
double |
0.0d |
decimal |
0.0m |
char |
'\0' |
bool |
false |
string |
null |
| أي نوع مرجعي | null |
▶ مثال
int defaultInt = default;
bool defaultBool = default;
char defaultChar = default;
string defaultString = default;
double defaultDouble = default;
Console.WriteLine($"int: [{defaultInt}]");
Console.WriteLine($"bool: [{defaultBool}]");
Console.WriteLine($"char: [{(int)defaultChar}]");
Console.WriteLine($"string: [{defaultString}]");
Console.WriteLine($"double: [{defaultDouble}]");
int: [0]
bool: [False]
char: [0]
string: []
double: [0]
نصيحة: يمكن للكلمة الرئيسية default الحصول على القيمة الافتراضية لأي نوع، وهي شائعة بشكل خاص في الأنواع العامة: default(T).
12. أنواع القيم مقابل أنواع المراجع — مقدمة
تنقسم أنواع C# إلى فئتين رئيسيتين:
| الفئة | التخزين | سلوك التخصيص | الأنواع النموذجية |
|---|---|---|---|
| نوع القيمة | المكدس (عادةً) | نسخ القيمة | int، bool، char، decimal، struct، enum |
| نوع المرجع | الذاكرة الديناميكية | نسخ المرجع (العنوان) | string، class، object، المصفوفات |
int a = 10;
int b = a;
b = 20;
Console.WriteLine($"a = {a}, b = {b}");
string s1 = "hello";
string s2 = s1;
s2 = "world";
Console.WriteLine($"s1 = {s1}, s2 = {s2}");
a = 10, b = 20
s1 = hello, s2 = world
تحذير: لم يتحول s1 إلى "world" لأن string غير قابل للتغيير — فـ s2 = "world" لا يُعدِّل الكائن الأصلي، بل يجعل s2 يشير إلى كائن جديد. تقوم عملية التعيين لنوع القيمة بنسخ القيمة الفعلية، بينما تقوم عملية التعيين لنوع المرجع بنسخ المرجع. ومع ذلك، فإن ثبات string يجعلها تتصرف كنوع قيمة.
❓ أسئلة شائعة
var وdynamic؟var يحدد النوع في وقت التحويل البرمجي ويتميز بالأمان من حيث النوع؛ أما dynamic فيحدد النوع في وقت التشغيل، ولا يخضع لفحص النوع في وقت التحويل البرمجي، ويكون أداؤه أقل، كما أنه أكثر عرضة للأخطاء.decimal أفضل من double في الحسابات النقدية؟decimal التخزين العشري، لذا يمكن تمثيل 0.1 بدقة؛ بينما يستخدم double التخزين الثنائي، حيث لا يمكن تمثيل 0.1 بدقة، مما يؤدي إلى أخطاء تقريب تراكمية.string نوع قيمة أم نوع مرجعي؟string هو نوع مرجعي، ولكن نظرًا لكونه غير قابل للتغيير، فإن عملية التعيين تتسم بدلالات القيمة — أي أن تعديل متغير ما لا يؤثر على متغير آخر.const وreadonly؟const هو ثابت وقت التحويل البرمجي الذي يجب تعيين قيمته عند الإعلان عنه، وهو ثابت ضمنيًا؛ أما readonly فهو ثابت وقت التشغيل يمكن تعيينه في منشئ وقد يختلف من مثيل لآخر.📖 ملخص
- لغة C# هي لغة ذات أنواع محددة بشكل صارم؛ يجب تحديد نوع المتغيرات عند إعلانها أو استخدام
varللاستدلال على النوع - تُكتب المتغيرات المحلية بأسلوب «camelCase»؛ أما الطرق والفئات فتُكتب بأسلوب «PascalCase»
- هناك 8 أنواع من الأعداد الصحيحة (بما في ذلك الأعداد غير الموقعة)؛
intهو النوع الأكثر استخدامًا - استخدم
decimalللأغراض المالية، وdoubleللحسابات العلمية، وfloatللرسومات - لا يمكن تحويل
boolضمناً إلى أعداد صحيحة أو منها؛charهو نوع يونيكود مكون من بايتين varيتطلب التهيئة، ويتم تحديد النوع في وقت التحويل البرمجيconstيُعرِّف الثوابت في مرحلة الترجمة (PascalCase)؛readonlyيُعرِّف الثوابت في مرحلة التشغيل- تسترد الكلمة الرئيسية
defaultالقيمة الافتراضية لأي نوع - التعيين من النوع القيمي يؤدي إلى نسخ القيمة؛ أما التعيين من النوع المرجعي فيؤدي إلى نسخ المرجع
📝 تمارين
- قم بتعريف متغير
intباسمageوقيمته هي عمرك، ومتغيرstringباسمnameوقيمته هي اسمك، ثم اعرض كليهما - احسب
0.1 + 0.2باستخدامfloatوdoubleوdecimalعلى التوالي، وقارن ما إذا كانت النتائج تساوي0.3تمامًا - استخدم
sizeofلطباعة أحجام البايتات الخاصة بـintوdoubleوcharوbool - حدد
const double Pi = 3.14159265واحسب مساحة الدائرة التي يبلغ نصف قطرها 5، ثم اعرض النتيجة - استخدم الكلمة الرئيسية
defaultللحصول على القيم الافتراضية لـintوboolوcharوstringوعرضها