الـ string هو نوع من البيانات بنستخدمه عشان نخزن فيه نصوص أو text. كل حرف في الـ string بيستهلك 2 Bytes من الذاكرة.
string greeting = "Hello";
string greeting2 = "Nice to meet you!";الـ string بيعتبر من الـ Reference Types، وده معناه إنه class مش مجرد نوع بيانات بسيط، والـ class ده بيكون جواه properties و methods نقدر نستخدمها.
Why is String a Reference Type?
في معظم لغات البرمجة، الـ string بيعتبر reference type مش value type. السبب الرئيسي هو إننا لما بنعرّف متغير string، مش بنبقى عارفين حجمه هيكون قد إيه بالظبط في الـ memory.
- لما بتعرّف متغير زي
int x، الـcompilerبيكون عارف إنه هيحجز4 bytesفي الـmemoryعلطول. - لكن لما بتعرّف
string str;، في اللحظة دي هو مجردreferenceأو مؤشر فاضي. لما بتخصص له قيمة زيstr = "ABC";، ساعتها بس بيتم حجز المساحة المطلوبة في الـmemory.
string str; // This is just a reference
// Here, memory is allocated for the value "ABC"
str = "ABC"; في C#، اللغة بتهتم بعملية الـ allocation دي تلقائيًا، فمش بنحتاج نستخدم كلمة new زي ما بنعمل مع الـ objects التانية.
string vs. String
يمكن تلاحظ إن string بتتكتب بطريقتين: string بحرف s صغير، وString بحرف S كبير.
الـ string (بحرف s صغير) هو مجرد alias أو اسم مختصر للـ class الأساسي اللي هو System.String (بحرف S كبير). من ناحية الأداء، مفيش أي فرق بينهم.
كممارسة كويسة (best practice):
- استخدم
stringلما تعرّف متغيرات. - استخدم
Stringلما تستدعيstatic methodsمن الـclassنفسه، زيString.Format()أوString.IsNullOrEmpty().
Immutability of Strings
واحدة من أهم خصائص الـ strings في C# هي إنها Immutable، يعني غير قابلة للتعديل.
لما بتعمل أي تعديل على string، زي إنك تضيف عليه نص أو تحذف منه جزء، اللي بيحصل في الحقيقة مش تعديل للـ string الأصلي، لأ، ده بيتم إنشاء string جديد خالص في الذاكرة بالنتيجة الجديدة، والـ reference بتاعك بيشاور على الـ object الجديد ده. الـ string القديم بيستنى الـ Garbage Collector عشان يمسحه.
ده بيأثر على أداء البرنامج والذاكرة، خصوصًا لو بتعمل عمليات تعديل متكررة في loop.
graph TD A[str = Hello] --> B(Memory: Hello); subgraph After Modification C[str = Hello World] --> D(Memory: Hello World); B[Becomes Garbage]; end
Performance Impact Example
لو شغلنا كود بيعمل loop عدد كبير من المرات وبيغير قيمة string في كل مرة، هنلاحظ إن العملية بتاخد وقت طويل جدًا.
using System;
using System.Diagnostics;
// ...
string str = "";
Console.WriteLine("Loop Started");
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 30000000; i++)
{
// A new string object is created in each iteration
str = Guid.NewGuid().ToString();
}
stopwatch.Stop();
Console.WriteLine("Loop Ended");
// Execution time will be very high (e.g., 26000 ms)
Console.WriteLine("Loop Execution Time in MS :" + stopwatch.ElapsedMilliseconds);
// Without changing => 48 ms
// with changing => 3800 msعلى عكس كدة، لو استخدمنا value type زي int، القيمة بتتعدل في نفس المكان في الذاكرة، وده بيخليه أسرع بكتير.
int ctr = 0;
Console.WriteLine("Loop Started");
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 30000000; i++)
{
// Value is modified in the same memory location
ctr = ctr + 1;
}
stopwatch.Stop();
Console.WriteLine("Loop Ended");
// Execution time will be very low (e.g., 84 ms)
Console.WriteLine("Loop Execution Time in MS :" + stopwatch.ElapsedMilliseconds);String Interning
طيب لو بنخصص نفس القيمة للـ string بشكل متكرر، هل برضه الأداء هيكون بطيء؟ الإجابة لأ، وده بسبب حاجة اسمها String Interning.
الـ .NET CLR بيحتفظ بـ pool أو مخزن للـ strings اللي تم استخدامها. لما بتيجي تنشئ string جديد، الأول بيشوف لو فيه string بنفس القيمة موجود بالفعل في الـ pool ده. لو موجود، بيرجعلك reference لنفس الـ object الموجود بدل ما ينشئ واحد جديد.
String Interning Example
لو شغلنا نفس الـ loop اللي فات بس بنخصص قيمة ثابتة كل مرة، هنلاقي الأداء اتحسن جدًا.
string str = "";
Console.WriteLine("Loop Started");
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 30000000; i++)
{
// The CLR reuses the same string from the intern pool
str = "DotNet Tutorials";
}
stopwatch.Stop();
Console.WriteLine("Loop Ended");
// Execution time is very low due to interning (e.g., 95 ms)
Console.WriteLine("Loop Execution Time in MS :" + stopwatch.ElapsedMilliseconds);Concatenation
لدمج النصوص (Concatenation)، بنستخدم علامة +.
string firstName = "Pop";
string lastName = "Corn";
string name = firstName + lastName; // Result: "PopCorn"
Console.WriteLine(name);ممكن كمان نستخدم الـ method اللي اسمها string.Concat().
string firstName = "Pop";
string lastName = "Corn";
string name = string.Concat(firstName, lastName);
Console.WriteLine(name);بس خلي بالك، دمج النصوص جوه loop باستخدام + بيسبب مشكلة أداء كبيرة بسبب الـ immutability، لإن في كل مرة بيتم إنشاء string جديد.
The Solution: StringBuilder
الحل الأمثل لعمليات الدمج المتكررة هو استخدام class اسمه Cs StringBuilder. الـ StringBuilder يعتبر mutable (قابل للتعديل)، يعني لما بتضيف عليه نص، هو بيعدّل على نفس الـ object في الذاكرة من غير ما ينشئ واحد جديد كل مرة.
Performance with StringBuilder
لو قارنا أداء StringBuilder مع الـ string العادي في عملية دمج متكررة، هنلاقي فرق ضخم.
using System.Text;
using System.Diagnostics;
// ...
StringBuilder stringBuilder = new StringBuilder();
Console.WriteLine("Loop Started");
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 30000; i++)
{
// Appends to the same object, very efficient
stringBuilder.Append("DotNet Tutorials");
}
stopwatch.Stop();
Console.WriteLine("Loop Ended");
// Execution time is extremely low (e.g., 1 ms)
Console.WriteLine("Loop Execution Time in MS :" + stopwatch.ElapsedMilliseconds);Why are Strings Immutable?
السؤال المهم، ليه أصلًا خلّوا الـ strings في C# بالصفة دي Immutable؟ السبب الرئيسي هو الأمان في بيئة تعدد الـ Threads أو Thread Safety.
لو كان الـ string قابل للتعديل (mutable)، وأكتر من Thread بيحاولوا يعدلوا على نفس الـ string object في نفس الوقت، ده كان هيسبب مشاكل كبيرة جدًا وتضارب في البيانات. كون الـ string غير قابل للتعديل بيخلي مشاركته بين الـ threads عملية آمنة تمامًا.
Common String Methods
Length
دي property بترجعلك عدد الحروف اللي في الـ string. لاحظ إننا مش بنكتب بعدها قوسين () لإنها property مش method.
string txt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Console.WriteLine(txt.Length); // Outputs: 26Formatting
-
الـ
ToUpper()وToLower(): بيحولوا كل الحروف لـUPPERCASEأوlowercase.string txt = "Hello World"; Console.WriteLine(txt.ToUpper()); // HELLO WORLD Console.WriteLine(txt.ToLower()); // hello world -
الـ
Trim(): بتشيل أي مسافات فاضية في أول أو آخر الـstring.string val1 = " a"; string val2 = "a "; Console.WriteLine(val1.Trim() == val2.Trim()); // True
Searching
-
الـ
Contains()،StartsWith()،EndsWith(): بتعمل بحث وبترجعtrueأوfalse.string pangram = "The quick brown fox jumps over the lazy dog."; Console.WriteLine(pangram.Contains("fox")); // True Console.WriteLine(pangram.Contains("cow")); // False -
الـ
IndexOf()وLastIndexOf(): بترجع الـindexبتاع أول أو آخر ظهور لحرف أو كلمة.string myString = "Hello"; Console.WriteLine(myString.IndexOf('e')); // Outputs: 1
Substrings
- الـ
Substring(startIndex): بتقطع الـstringمنindexمعين لحد الآخر. - الـ
Substring(startIndex, length): بتقطع جزء من الـstringبتبدأه منindexمعين وبطول محدد.
// Full name
string name = "John Doe";
// Location of the letter D
int charPos = name.IndexOf("D");
// Get last name from charPos to the end
string lastName = name.Substring(charPos);
// Print the result
Console.WriteLine(lastName); // Outputs: DoeReplacing
- الـ
Replace('a', '!'): بتبدل كل حروفaبعلامة!. - الـ
Replace("old", "new"): بتبدل كلمة بكلمة تانية.
Null Checking
- الـ
String.IsNullOrEmpty(str): بتتحقق لو الـstringكانnullأو فاضي"". - الـ
String.IsNullOrWhiteSpace(str): بتتحقق لو كانnullأو فاضي أو بيحتوي على مسافات بس.
Splitting
- الـ
str.Split(' '): بتقسم الـstringلمصفوفة من الـstringsبناءً على فاصل معين (زي المسافة).
Converting Types
نقدر نحول من أرقام لـ string والعكس باستخدام Explicit Casting.
أو ممكن نستخدم i.ToString(format) عشان نتحكم في شكل الـ string الناتج.

- ملحوظة: الفورمات
C0في المثال معناهCurrency(عملة) مع0أرقام بعد العلامة العشرية. لو استخدمتC1هيظهر رقم واحد بعد العلامة، وهكذا.
String Formatting
فيه طرق مختلفة عشان ننسق شكل النصوص النهائية.
-
String.Format:
string str = String.Format("Hello World {0}", x); Console.Write(String.Format("Hello World {0}", x)); -
String Interpolation (
$): دي الطريقة الأحدث والأسهل.string str = $"Hello World {x}"; -
الـ Verbatim Identifier (
@): بتخلي الـstringيتطبع زي ما هو بالظبط من غير ما يعملescapeلأيcharactersزي\. مفيدة جدًا في مسارات الملفات.Console.WriteLine($@"C:\Output\{projectName}\Data");
Accessing Strings
ممكن نوصل لأي حرف في الـ string عن طريق الـ index بتاعه، زي الـ array بالظبط.
string myString = "Hello";
Console.WriteLine(myString[0]); // Outputs: H
Console.WriteLine(myString[1]); // Outputs: eEmpty String
أحيانًا بنحتاج نبدأ بـ string فاضي عشان نجمع فيه قيم بعدين. أفضل طريقة لعمل ده هي باستخدام string.Empty لإنها أوضح ومش بتعمل allocation جديدة في الذاكرة.
string numbers = string.Empty;
for (int i = 0; i < 10; i++)
{
numbers = numbers + i.ToString();
}