تخطَّ إلى المحتوى

النصوص في C#‎: string وأهم دوالها

الدرس 8 من 11 · C#

اسم اللاعب في لعبتك، ورسالة الخطأ في برنامجك، وعنوان النافذة: كلها نصوص. استخدمت string والنصوص المُدرَجة بعلامة $ منذ الدروس الأولى. في هذا الدرس ستتعلم أهم دوال النصوص في C#‎: كيف تقطع النص وتبحث فيه وتقارنه وتنظفه، ومتى تحتاج StringBuilder.

النص سلسلة حروف، ولكل حرف رقم يبدأ من صفر، مثل المصفوفات تماماً:

string word = "Unity";
Console.WriteLine(word.Length);
Console.WriteLine(word[0]);
Console.WriteLine(word[^1]);
Console.WriteLine(word[1..4]);
الناتج
5
U
y
nit
  • Length: عدد الحروف، وهي خاصية بدون أقواس.
  • word[0]: الحرف الأول، ونوعه char.
  • word[^1]: الحرف الأول من النهاية، أي الأخير.
  • word[1..4]: من الموضع 1 إلى ما قبل الموضع 4.

تخيّل النص مثل صف مقاعد في السينما: لكل مقعد رقم، وفي كل مقعد حرف واحد.

النص في C#‎ غير قابل للتعديل (Immutable). أي دالة «تغيّر» النص تُرجع نصاً جديداً وتترك الأصلي كما هو:

string name = "sara";
name.ToUpper();
Console.WriteLine(name);
string upper = name.ToUpper();
Console.WriteLine(upper);
الناتج
sara
SARA

السطر الثاني بلا فائدة، لأننا لم نخزن النتيجة في متغير.

string text = " Hello World ";
string clean = text.Trim();
Console.WriteLine($"[{clean}]");
Console.WriteLine(clean.Substring(6));
Console.WriteLine(clean.Substring(0, 5));
Console.WriteLine(clean.ToLower());
Console.WriteLine(clean.Replace("World", "C#"));
Console.WriteLine("7".PadLeft(3, '0'));
الناتج
[Hello World]
World
Hello
hello world
Hello C#
007
  • Trim(): تحذف المسافات من الطرفين. استخدمها دائماً مع ما يكتبه المستخدم.
  • Substring(6): من الموضع 6 إلى النهاية.
  • Substring(0, 5): من الموضع 0 وبطول 5 حروف. انتبه: الرقم الثاني هنا طول وليس موضع نهاية.
  • Replace(a, b): تستبدل كل ظهور للنص الأول بالثاني.
  • PadLeft(3, '0'): تكمل النص من اليسار حتى يصل طوله إلى 3.
string email = "[email protected]";
Console.WriteLine(email.Contains("@"));
Console.WriteLine(email.IndexOf("@"));
Console.WriteLine(email.IndexOf("#"));
Console.WriteLine(email.StartsWith("omar"));
Console.WriteLine(email.EndsWith(".org"));
int at = email.IndexOf("@");
Console.WriteLine(email[..at]);
Console.WriteLine(email[(at + 1)..]);
الناتج
True
4
-1
True
False
omar
example.com

IndexOf تُرجع موضع أول ظهور، أو -1 إذا لم تجد شيئاً. ومع النطاقات [..at] و [(at + 1)..] استخرجنا اسم المستخدم والنطاق بسهولة.

العلامة == في C#‎ تقارن محتوى النصين مباشرة، فلا حاجة لدالة خاصة كما في Java:

string a = "unity";
string b = "UNITY".ToLower();
Console.WriteLine(a == b);
Console.WriteLine(a == "Unity");
Console.WriteLine(a.Equals("Unity", StringComparison.OrdinalIgnoreCase));
Console.WriteLine(string.Compare("apple", "banana") < 0);
الناتج
True
False
True
True
  • ==: هل المحتوى متطابق؟ والمقارنة حساسة لحالة الحروف.
  • StringComparison.OrdinalIgnoreCase: لتجاهل حالة الحروف. أفضل من تحويل النصين بـ ToLower ثم مقارنتهما.
  • string.Compare: مقارنة أبجدية؛ ناتج سالب يعني أن الأول يأتي قبل الثاني.

حقل الإدخال قد يصلك فارغاً، أو مسافات فقط، أو null. دالة واحدة تفحص الحالات الثلاث:

string?[] inputs = { "علي", "", " ", null };
foreach (string? input in inputs)
{
if (string.IsNullOrWhiteSpace(input))
{
Console.WriteLine("الاسم مطلوب");
}
else
{
Console.WriteLine($"مرحباً {input}");
}
}
الناتج
مرحباً علي
الاسم مطلوب
الاسم مطلوب
الاسم مطلوب
string line = "تفاح,موز,عنب";
string[] fruits = line.Split(',');
Console.WriteLine(fruits.Length);
Console.WriteLine(fruits[1]);
string joined = string.Join(" - ", fruits);
Console.WriteLine(joined);
الناتج
3
موز
تفاح - موز - عنب

Split تقطع النص عند الفاصل وتُرجع مصفوفة، و string.Join تفعل العكس. ستحتاجهما عند قراءة ملفات البيانات أو إدخال المستخدم.

النصوص الحرفية ومتعددة الأسطر

Section titled “النصوص الحرفية ومتعددة الأسطر”

الشرطة المائلة \ لها معنى خاص داخل النص (\n سطر جديد)، وهذا مزعج مع مسارات ويندوز. العلامة @ قبل النص تلغي هذا المعنى، وثلاث علامات تنصيص تكتب نصاً على عدة أسطر:

string path = @"C:\Users\Omar\notes.txt";
Console.WriteLine(path);
string player = "ليلى";
string message = $"""
مرحباً {player}،
نتيجتك: {40 + 2}
""";
Console.WriteLine(message);
الناتج
C:\Users\Omar\notes.txt
مرحباً ليلى،
نتيجتك: 42

النص الخام """ (منذ C#‎ 11) يحذف الإزاحة المشتركة تلقائياً، ويقبل الإدراج إذا سبقته $.

StringBuilder: بناء النصوص بكفاءة

Section titled “StringBuilder: بناء النصوص بكفاءة”

لأن النص لا يتغير، فكل + داخل حلقة ينشئ نصاً جديداً كاملاً ويرمي القديم. مع آلاف التكرارات يصبح ذلك بطيئاً. الحل هو StringBuilder: نص قابل للتعديل تضيف إليه، ثم تحوّله إلى string في النهاية:

using System.Text;
var sb = new StringBuilder();
for (int i = 1; i <= 5; i++)
{
sb.Append(i);
sb.Append(' ');
}
sb.AppendLine("!");
sb.Append("النهاية");
Console.WriteLine(sb.ToString());
Console.WriteLine(sb.Length);
الناتج
1 2 3 4 5 !
النهاية
20

الفرق مثل كتابة كل مسودة على ورقة جديدة ورمي السابقة، مقابل الكتابة على سبورة تضيف إليها كما تشاء. لدمج نصين أو ثلاثة يكفي + أو $، أما داخل الحلقات فاستخدم StringBuilder.

  1. خزّن جملة في متغير، واطبع عدد حروفها وعدد كلماتها باستخدام Split(' ').
  2. خزّن اسم ملف مثل photo.png، واستخرج الامتداد باستخدام LastIndexOf('.') والنطاقات.
  3. اكتب دالة Initials(string fullName) تُرجع الحرف الأول من كل كلمة: "Omar Ali" تصبح "OA".
  4. تحدٍّ: اكتب دالة IsPalindrome(string word) تُرجع true إذا كانت الكلمة تُقرأ من الجهتين بنفس الشكل مثل level، مع تجاهل حالة الحروف.
  • تجاهل القيمة المُرجعة: text.Trim(); وحدها لا تغيّر text. اكتب text = text.Trim();.
  • الخلط في Substring: "Hello".Substring(1, 3) تُرجع ell (ثلاثة حروف)، وليس الحروف من 1 إلى 3. الرقم الثاني طول.
  • موضع خارج الحدود: "Unity"[5] يرمي IndexOutOfRangeException، فآخر موضع هو 4.
  • استدعاء دالة على null: name.Length و name قيمته null يرمي NullReferenceException. افحص أولاً، أو استخدم name?.Length.

سؤال

ما ناتج "Hello".Substring(1, 3)؟

سؤال

أي دالة تفحص أن النص ليس null وليس فارغاً وليس مسافات فقط؟

  • النص سلسلة حروف مرقمة من صفر: Length و [i] و [^1] والنطاقات [a..b].
  • النصوص لا تتغير: كل دالة تُرجع نصاً جديداً يجب أن تخزنه.
  • == تقارن المحتوى، و OrdinalIgnoreCase تتجاهل حالة الحروف.
  • Split و string.Join للتقسيم والدمج، و IsNullOrWhiteSpace لفحص الحقول.
  • لبناء نص داخل حلقة استخدم StringBuilder ثم ToString().