لغة C# تعتبر لغة Strongly typed language، وده معناه إنك لازم تحدد نوع البيانات اللي هتخزنها في أي متغير (variable) بتعرفه. اختيارك لنوع البيانات المناسب مهم جدًا، مش بس عشان الكود يشتغل صح، لكن كمان عشان أداء البرنامج (performance) يكون أفضل.

Why Do We Need Data Types?

في حياتنا بنتعامل مع أنواع مختلفة من البيانات زي الأرقام الصحيحة، الأرقام العشرية، الحروف، والنصوص. عشان نقدر نخزن ونتعامل مع كل الأنواع دي في البرامج بتاعتنا، بنحتاج نستخدم Data Types.

نوع البيانات في C# بيدينا معلومات عن:

  • حجم المكان اللي هيتحجز في الذاكرة.
  • المدى (Range) بتاع القيم اللي ممكن تتخزن في المكان ده.
  • العمليات المسموح بيها على النوع ده من البيانات.

Classification of Data Types in C#

أنواع البيانات في C# بتتقسم لـ 3 فئات رئيسية:

  1. Value Data Types
  2. Reference Data Types
  3. Pointer Data Types
graph TD
    A[Data Types in C#] --> B[Value Types];
    A --> C[Reference Types];
    A --> D["Pointer Types (Advanced)"];

    B --> B1["Predefined (Built-in)"];
    B --> B2["User-defined (struct, enum)"];

    C --> C1["Predefined (string, object)"];
    C --> C2["User-defined (class, interface, array)"];

How Data is Represented in a Computer

قبل ما ندخل في تفاصيل أنواع البيانات، خلينا نفهم الأول إزاي الكمبيوتر بيشوف الداتا دي.

تخيل إن عندك على الهارد ديسك بتاعك أي نوع من البيانات، زي:

  • صورة
  • حرف
  • أرقام
  • ملف PDF

هنفترض إن الداتا دي هي حرف ‘A’. إحنا عارفين إن الكمبيوتر مبيفهمش غير لغة الأرقام الثنائية (binary)، اللي هي عبارة عن أصفار ووحايد (0 و 1).

عشان كده، حرف ‘A’ بيتمثل جوه الكمبيوتر في شكل 8 bits، اللي هما 01000001. طب الرقم ده جه منين؟

  1. كل حرف ليه قيمة مقابلة في جدول اسمه ASCII. القيمة العشرية (decimal) لحرف ‘A’ هي 65.
  2. الكمبيوتر بياخد الرقم العشري 65 ده ويحوله للمعادل الثنائي (binary) بتاعه، اللي هو 01000001.

الأصفار والوحايد دي بنسميها bits. والمجموعة الكاملة المكونة من 8 bits بنسميها Byte.

كمطورين، صعب جدًا نتعامل مع الداتا بصيغة الـ binary دي مباشرةً. عشان كده، لغة C# بتسهل علينا الموضوع وبتخلينا نستخدم الأرقام العشرية. اللي بيحصل إننا بنتعامل مع الرقم العشري (زي 65)، والكمبيوتر داخليًا بيقوم بتحويله لشكل الـ Byte (الصيغة الثنائية) عشان يقدر يمثل الداتا.

دلوقتي، خلينا نبدأ ونفهم أنواع البيانات المختلفة اللي بيوفرها الـ .NET Framework عشان نستخدمها في لغة C#.

Value Data Types

الـ Value Types بتخزن القيمة بتاعتها مباشرةً في مكان في الذاكرة اسمه الـ Stack. دي بتشمل الأنواع الأساسية اللي جاية مع اللغة، وكمان الأنواع اللي بنعرفها باستخدام struct و enum.

Predefined / Built-in Types

دي الأنواع الأساسية الجاهزة في C#.

Integer Types (الأعداد الصحيحة)

دي بتستخدم للأرقام اللي مفيهاش كسور. أشهرهم بيختلفوا في المساحة اللي بياخدوها في الذاكرة وقدرتهم على تخزين الأرقام.

  • الـ byte: بيخزن أرقام موجبة بس من 0 لـ 255 (بياخد 1 بايت).
  • الـ sbyte: بيخزن أرقام موجبة وسالبة من -128 لـ 127 (بياخد 1 بايت).
  • الـ short (أو Int16): بياخد 2 بايت.
  • الـ int (أو Int32): هو أشهر نوع وبيستخدم في معظم الحالات (بياخد 4 بايت).
  • الـ long (أو Int64): بيستخدم للأرقام الكبيرة جدًا (بياخد 8 بايت).
// byte: Stores whole numbers from 0 to 255 (unsigned)
byte age = 25;
Console.WriteLine($"Size of byte: {sizeof(byte)} byte"); // Output: 1
 
// int: Most commonly used integer type
int population = 100_000_000; // Using _ for readability
Console.WriteLine($"Size of int: {sizeof(int)} bytes"); // Output: 4
 
// long: For very large whole numbers, 'L' suffix is needed
long veryLargeNumber = 9_000_000_000_000_000_000L;
Console.WriteLine($"Size of long: {sizeof(long)} bytes"); // Output: 8

Unsigned Types

لو متأكد إن المتغير بتاعك عمره ما هيشيل قيمة سالبة، ممكن تستخدم الأنواع الـ unsigned عشان تستغل المدى كله في الأرقام الموجبة.

  • ushort, uint, ulong.
// uint: Unsigned integer (0 to ~4.2 billion)
uint positiveCounter = 1000;
Console.WriteLine($"uint MaxValue: {uint.MaxValue}");

Real / Fractional Types (الأعداد العشرية)

بنستخدمها للأرقام اللي فيها كسور.

  • الـ float (أو Single): دقته أقل، لازم تحط f بعد الرقم (بياخد 4 بايت).
  • الـ double (أو Double): هو النوع الافتراضي للأرقام العشرية، دقته متوسطة (بياخد 8 بايت).
  • الـ decimal: دقته عالية جدًا وبيستخدم في الحسابات المالية، لازم تحط m بعد الرقم (بياخد 16 بايت).
// double: Default type for fractional numbers
double averageScore = 85.7;
 
// float: Less precision than double, requires 'f' suffix
float itemPrice = 19.99f;
 
// decimal: High precision, for financial calculations, requires 'm' suffix
decimal accountBalance = 12345.67m;
Console.WriteLine($"Size of decimal: {sizeof(decimal)} bytes"); // Output: 16

Character Type (char)

بنستخدم char عشان نخزن حرف واحد بس، ولازم يتحط بين علامتين تنصيص فرديتين (' '). بياخد 2 بايت في الذاكرة لأنه بيستخدم نظام Unicode عشان يدعم كل حروف لغات العالم.

// char: Stores a single Unicode character
char grade = 'A';
char symbol = 'ش';
Console.WriteLine($"Size of char: {sizeof(char)} bytes"); // Output: 2

Boolean Type (bool)

بنستخدم bool عشان نخزن قيمة من اتنين بس: true أو false. مينفعش تستخدم 0 أو 1 بدلهم.

bool isActive = true;
bool hasPermission = false;
Console.WriteLine($"Size of bool: {sizeof(bool)} byte"); // Output: 1

DateTime Type

ده struct بنستخدمه عشان نخزن تاريخ ووقت معين.

// DateTime: Represents a specific point in time
DateTime now = DateTime.Now; // Gets the current date and time
DateTime birthDate = new DateTime(1995, 6, 15); // June 15, 1995
Console.WriteLine($"Current time: {now}");

Reference Data Types

الـ Reference Types مش بتخزن القيمة مباشرةً، لكن بتخزن عنوان (reference) لمكان القيمة دي في منطقة في الذاكرة اسمها الـ Heap.

graph TD
    subgraph Memory
        subgraph Stack
            A["<b>Value Type (int)</b><br>myValue = 100;"]
            B["<b>Reference Type (Vehicle)</b><br>myCarRef --> Address 0x123"]
        end
        subgraph Heap
            C["<b>Object Data @ 0x123</b><br>Id=1, Make='Toyota'"]
        end
    end

Predefined Reference Types

String Type

الـ string بنستخدمه عشان نخزن سلسلة من الحروف (نصوص)، ولازم يتحط بين علامتين تنصيص مزدوجتين (" "). مع إنه reference type (لأنه class)، إلا إن ليه شوية خصائص بتخليه شبه الـ value types في التعامل.

// string: Stores a sequence of characters
string name = "Mahmoud Feshar";
 
// Using @ for a verbatim string literal (ignores escape characters like \)
string filePath = @"C:\Users\Mahmoud\Documents";
Console.WriteLine(filePath);

User-Defined Reference Types

دي الأنواع اللي المبرمج بيعملها بنفسه عشان تمثل مفاهيم معقدة في البرنامج. أشهرهم الـ class.

// Defining a blueprint for Vehicle objects
public class Vehicle
{
    // Fields (data members)
    public int Id;
    public string Make = ""; // Good practice to initialize strings
 
    // Method (behavior)
    public void DriveVehicleForward()
    {
        Console.WriteLine($"Driving the {Make} forward!");
    }
}
 
// How to use it:
// Vehicle myCar = new Vehicle(); // Creates an object (instance) in the Heap
// myCar.Make = "Toyota";
// myCar.DriveVehicleForward();

Practical Considerations and Tools

Performance Matters: Choosing the Right Type

اختيار نوع البيانات بيفرق في أداء البرنامج. استخدام نوع بيانات أكبر من اللي محتاجه بيستهلك ذاكرة ووقت معالجة أزيد بدون داعي.

مثال: لو عندك رقم صغير زي 100، تخزينه في short أسرع من تخزينه في decimal.

// This is a simplified concept demonstration.
// In a loop that runs millions of times:
// Using 'short' can be faster than using 'decimal' for the same small number.
Stopwatch stopwatch1 = new Stopwatch();
stopwatch1.Start();
for(int i = 0; i < 10000000; i++)
{
    short s1 = 100;
}
stopwatch1.Stop();
Console.WriteLine($"short took : {stopwatch1.ElapsedMilliseconds} MS");
 
Stopwatch stopwatch2 = new Stopwatch();
stopwatch2.Start();
for (int i = 0; i < 10000000; i++)
{
    decimal d1 = 100;
}
stopwatch2.Stop();
Console.WriteLine($"decimal took : {stopwatch2.ElapsedMilliseconds} MS");

النتيجة بتوضح إن short أسرع.

Getting Type Information

ممكن تعرف معلومات عن أي نوع بيانات:

  • الـ sizeof(type): عشان تعرف حجم النوع بالـ byte.
  • الـ type.MinValue و type.MaxValue: عشان تعرف أقل وأكبر قيمة ممكن يشيلها.
  • الـ default(type): عشان تعرف القيمة الافتراضية للنوع ده (مثلاً 0 للأرقام، false للـ bool).
Console.WriteLine($"Size of Integer: {sizeof(int)}"); // 4
Console.WriteLine($"Integer Min/Max: {int.MinValue} / {int.MaxValue}");
Console.WriteLine($"Default Value of Boolean: {default(bool)}"); // False

C# Aliases vs. .NET Types

كل نوع أساسي في C# (زي int) ليه اسم مقابل في .NET (زي System.Int32). ده بيساعد اللغات المختلفة اللي بتشتغل على .NET إنها تفهم بعض. الأفضل تستخدم الأسماء المختصرة بتاعة C# (int, string, …) عشان الكود يبقى أسهل في القراية.

Numeric Formatting

لما بتطبع أرقام، ممكن تتحكم في شكلها باستخدام format specifiers.

int X = 10;
int Y = 255;
 
// {Variable,Alignment:Format}
// X,5  => Print X in a space of 5 characters, right-aligned (default)
// Y,-5 => Print Y in a space of 5 characters, left-aligned
// X+Y:X => Print the sum in Hexadecimal format (uppercase X)
Console.WriteLine($"Equation : {X,5} + {Y,-5} = {X + Y:X}");
// Output: Equation :    10 + 255   = 109   (Note: 10 + 255 = 265, which is 109 in Hex)
 
Console.WriteLine($"Currency : {123.456:C}"); // Output: Currency : $123.46 (depends on system culture)
Console.WriteLine($"Percentage:{0.85:P1}");  // Output: Percentage:85.0 % (P1 means 1 decimal place)
Console.WriteLine($"Fixed Point:{12345.6789:F2}");// Output: Fixed Point:12345.68 (F2 means 2 decimal places)
  • الرقم اللي بعد الفاصلة (زي 5 مع الـ X) بيحدد عرض الحقل اللي هيطبع فيه الرقم. لو الرقم موجب، بيحصل right alignment (الرقم بيبقى على اليمين والمسافات قبله). لو الرقم سالب (زي -5 مع الـ Y)، بيحصل left alignment (الرقم بيبقى على الشمال والمسافات بعده).
  • الحرف اللي بعد النقطتين (: زي X أو C أو P) بيحدد شكل الفورمات (Hex, Currency, Percent, …).

Digit Separator (_)

عشان تخلي الأرقام الطويلة سهلة في القراية، C# بتسمحلك تستخدم الشرطة السفلية (_) كفاصل. الكومبايلر بيتجاهلها تمامًا.

int largeNumber = 1_000_000_000;
byte binaryValue = 0b_0101_1100;

Pointer Types (Advanced)

الـ Pointer هو متغير بيخزن عنوان الذاكرة (memory address) بتاع متغير تاني. ده موضوع متقدم ومش بيستخدم كتير في C# العادية، وبيحتاج إنك تشغل الكود في وضع unsafe.

unsafe
{
    // declare a variable
    int number = 10;
    // store variable number address location in pointer variable ptr
    int* ptr = &number;
    Console.WriteLine($"Value: {number}");
    Console.WriteLine($"Address: {(long)ptr}"); // Print memory address
}

عشان الكود ده يشتغل، لازم تعلم على خيار “Allow unsafe code” في إعدادات المشروع بتاعك.