بررسی عمیق string در سی شارپ

در این مقاله، قصد دارم با مثالهایی در مورد رشته در سی شارپ به صورت عمیق بحث کنم. به عنوان یک برنامه نویس سی شارپ، درک مفهوم رشته ها در سی شارپ بسیار مهم است زیرا در پروژه های مختلف string ها بسیار کاربردی و پر استفاده هستند بنابراین در این مقاله، قصد داریم نکات زیر را با مثال های مختلف توضیح دهیم.
- رشته ها از نوع ارجاعی(reference type) هستند.
- تفاوت string و String را بدانیم.
- رشته ها در سی شارپ immutable هستند.
- چگونه String Intern عملکرد رشته ها را بهبود می بخشد؟
- استفاده ازStringBuilder برای الحاق رشته ها
- دلیل طراحی String ها به صورت immutable
رشته ها از نوع ارجاعی(reference type) هستند :
رشته های سی شارپ object هستند، یعنی نوع داده های معمولی نیستند. برای مثال، اگر برخی از متغیرها را با استفاده از انواع داده های int یا double مانند نمونه زیرتعریف کنیم

سپس اگر بر روی نوع داده کلیک راست کرده و به محل تعریف آن بروید، مطابق تصویر زیر خواهید دید که از نوع ساختار(struct) هستند. structها از نوع value type می باشند.

از طرف دیگر، اگر متغیری را از نوع داده رشته ای مطابق شکل زیر تعریف کنید.

سپس اگر بر روی نوع داده کلیک راست کرده و به محل تعریف آن بروید، مطابق تصویر زیر خواهید دید که از نوع کلاس هستند. کلاس ها از نوع ارجاعی(reference type) هستند و string نیز یک نوع ارجاعی می باشد.

بنابراین، اولین نکته ای که باید به خاطر بسپارید این است که رشته ها از نوع ارجاعی هستند در حالی که سایر انواع داده های اولیه از نوع ساختاری یعنی value type می باشند.
تفاوت string و String :
در سی شارپ، میتوانید از رشته ها به هر دو صورت استفاده کنید، یعنی میتوانید از رشته با استفاده از S بزرگ (String) یا با استفاده از «s» کوچک (string) مانند تصویر زیر استفاده کنید.

اکنون سوالی که باید به ذهن شما خطور کند این است که تفاوت این دو (string و string) در سی شارپ چیست. string در واقع نام مستعار String است. اگر روی رشته کوچک راست کلیک کنید و اگر به محل تعریف آن بروید، می بینید که نام کلاس اصلی String است.

شما می توانید از هردو آنها استفاده کنید. اما طبق قرارداد نامگذاری زمانی که یک متغیر را ایجاد می کنید ازstring استفاده کنید و زمانی که می خواهید متدهای کلاس رشته را فراخوانی کنید، از String استفاده کنید.

رشته ها در سی شارپ immutable هستند :
ابتدا باید دو اصطلاح Mutable و Immutable را درک کنیم. Mutable را می توان تغییر داد در حالی که Immutable قابل تغییر نیست. رشته های سی شارپ Immutable هستند به این معنی که قابل تغییر نیستند. اجازه دهید با یک مثال این را بفهمیم. لطفا به تصویر زیر نگاه کنید. هنگامی که اولین دستور اجرا می شود، یک شی ایجاد شده و مقدار DotNet به آن اختصاص داده می شود. اما وقتی دستور دوم اجرا میشود، شی اول بازنویسی نمی شود بلکه توسط garbage collection حذف می شود و یک شی تازه ایجاد شده و مقدار Tutorials به آن اختصاص داده می شود.

بنابراین، هنگامی که دو دستور بالا اجرا می شوند، در داخل حافظه دو مکان ایجاد می شود. یکی با مقدار DotNet و دیگری با مقدار Tutorials و آخری قرار است به برنامه ارجاع داده شود. بنابراین، هر بار یک مقدار جدید به متغیر رشته اختصاص می دهیم، یک شی جدید ایجاد می شود و به همین دلیل است که رشته ها در سی شارپ تغییرناپذیر هستند.
اما این مورد در مورد value type ها صدق نمی کند. برای مثال، لطفاً به دو عبارت زیر نگاهی بیندازید. هنگامی که دستور اول اجرا می شود، یک مکان حافظه ایجاد می شود و مقدار 100 به آن اختصاص داده می شود ، زمانی که دستور دوم اجرا می شود، یک مکان حافظه جدید ایجاد نمی شود ، بلکه مقدار همان مکان حافظه را بازنویسی می کند.
مثالی برای اثبات غیرقابل تغییر بودن رشته های C#
اجازه دهید مثالی ببینیم تا متوجه شویم رشتههای #C غیرقابل تغییر هستند. لطفا کد زیر را کپی کنید. همانطور که می بینید در اینجا ما یک حلقه سنگین داریم. به عنوان بخشی از حلقه، مقداری را به متغیر string str اختصاص می دهیم. در اینجا، ما از GUID برای تولید یک مقدار جدید استفاده می کنیم و هر بار مقدار جدیدی ایجاد می شود و به متغیر str اختصاص داده می شود. از Stopwatch برای بررسی مدت زمان اجرای حلقه استفاده می کنیم.
using System;
using System.Diagnostics;
namespace StringDemo
{
class Program
{
static void Main(string[] args)
{
string str = "";
Console.WriteLine("Loop Started");
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 30000000; i++)
{
str = Guid.NewGuid().ToString();
}
stopwatch.Stop();
Console.WriteLine("Loop Ended");
Console.WriteLine("Loop Exceution Time in MS :" + stopwatch.ElapsedMilliseconds);
Console.ReadKey();
}
}
}
خروجی: زمانی که برنامه را اجرا می کنید، خروجی زیر را دریافت خواهید کرد. زمان ممکن است در دستگاه شما متفاوت باشد.

همانطور که در خروجی بالا مشاهده می کنید، اجرای حلقه تقریباً 26000 میلی ثانیه طول کشید. هر بار که حلقه اجرا می شود، یک شی رشته تازه ایجاد می شود و مقدار جدیدی به آن اختصاص داده می شود.این به دلیل immutable بودن رشته ها در سی شارپ می باشد.
اگر همین مثال را با int تست کتید خواهید دید که زمان اجرای آن بسیار سریع می باشد.
در یک مثال دیگر اگر بخواهیم درون حلقه هربار string را با مقدار قبلی جایگزین کنیم زمان اجرا تغییر خواهد کرد بیایید با یک مثال دیگر آن را بررسی کنیم.
using System;
using System.Diagnostics;
namespace StringDemo
{
class Program
{
static void Main(string[] args)
{
string str = "";
Console.WriteLine("Loop Started");
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 30000000; i++)
{
str ="DotNet Tutorials";
}
stopwatch.Stop();
Console.WriteLine("Loop Ended");
Console.WriteLine("Loop Exceution Time in MS :" + stopwatch.ElapsedMilliseconds);
Console.ReadKey();
}
}
}
خروجی :

چون در این مورد هر بار که حلقه اجرا می شود، اشیاء تازه ایجاد نمی شوند. اکنون سوالی که باید به ذهن شما خطور کند این است که چرا؟ پاسخ String intern است. بنابراین، اجازه دهید تا String intern را با جزئیات درک کنیم.
String Intern در سی شارپ:
String Intern در سی شارپ فرآیندی است که در صورت یکسان بودن مقدار، از همان مکان حافظه استفاده می کند. در مثال ما، هنگامی که حلقه برای اولین بار اجرا می شود، یک شی تازه ایجاد می کند و مقدار DotNet Tutorials را به آن اختصاص می دهد. وقتی حلقه برای بار دوم اجرا میشود، قبل از ایجاد یک شی تازه، بررسی میکند که آیا این مقدار DotNet Tutorials از قبل در حافظه وجود دارد یا خیر، اگر بله، آنگاه به سادگی از آن مکان حافظه استفاده میکند، در غیر این صورت یک مکان حافظه جدید ایجاد میکند.
بنابراین، اگر یک حلقه for را اجرا میکنید و بارها و بارها همان مقدار را اختصاص میدهید، از String Intern برای بهبود عملکرد استفاده میکند. در این حالت، به جای ایجاد یک شی جدید، از همان مکان حافظه استفاده می کند. اما هنگامی که مقدار تغییر می کند، یک شی تازه جدید ایجاد می کند و مقدار را به شی جدید اختصاص می دهد.
استفاده ازStringBuilder برای الحاق رشته ها :
همانطور که قبلاً بحث کردیم اگر مقدار تغییر کند، هر بار یک شی تازه جدید در C# ایجاد می شود و این به دلیل رفتار Immutability رشته است. وقتی صحبت از الحاق رشته ها به میان می آید رفتار تغییرناپذیری رشته ها در سی شارپ می تواند بسیار خطرناک باشد . اجازه دهید الحاق رشته ها در سی شارپ را با یک مثال درک کنیم و متوجه مشکل بشویم. در مثال زیر، رشته را با استفاده از حلقه for به هم متصل می کنیم.
using System;
using System.Diagnostics;
namespace StringDemo
{
class Program
{
static void Main(string[] args)
{
string str = "";
Console.WriteLine("Loop Started");
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 30000; i++)
{
str ="DotNet Tutorials" + str;
}
stopwatch.Stop();
Console.WriteLine("Loop Ended");
Console.WriteLine("Loop Exceution Time in MS :" + stopwatch.ElapsedMilliseconds);
Console.ReadKey();
}
}
}
خروجی :

همانطور که در تصویر بالا مشاهده می کنید، اجرای حلقه تقریباً 5473 میلی ثانیه طول کشید. برای اینکه بفهمید چگونه حلقه را اجرا می کند، لطفاً به تصویر زیر نگاه کنید. حلقه برای اولین بار اجرا می شود، یک مکان حافظه جدید ایجاد می کند و مقدار DotNet Tutorials را ذخیره می کند. برای بار دوم، یک مکان حافظه تازه دیگر (شیء تازه) ایجاد می کند و مقدار “DotNet Tutorials DotNet Tutorials” را ذخیره می کند و اولین مکان حافظه را garbage collection حذف می کند . و همین روند ادامه خواهد داشت، یعنی هر بار که حلقه اجرا میشود، یک مکان حافظه جدید ایجاد میشود و موارد قبلی توسط garbage collection حذف میشوند.

برای حل مشکل الحاق رشته ها در مثال بالا در سی شارپ، .NET Framework کلاس StringBuilder را ارائه می دهد. همانطور کهاز نامش مشخص است، کلاس string builder در سی شارپ برای ساخت یک رشته استفاده می شود. اگر از String builder استفاده می کنید، هر بار که چیزی را به متغیر رشته در سی شارپ الحاق می کنید، اشیاء تازه ایجاد نمی شوند.
مثالی برای استفاده از StringBuilder :
اجازه دهید نحوه غلبه بر مشکل الحاق رشته ها در سی شارپ را با استفاده از کلاس StringBuilder درک کنیم. در مثال زیر، ما از کلاس StringBuilder برای به هم پیوستن رشته ها استفاده می کنیم. در اینجا ابتدا یک نمونه از کلاس StringBuilder ایجاد می کنیم و سپس از متد Append کلاس StringBuilder برای به هم پیوستن رشته استفاده می کنیم.
using System;
using System.Diagnostics;
using System.Text;
namespace StringDemo
{
class Program
{
static void Main(string[] args)
{
StringBuilder stringBuilder = new StringBuilder();
Console.WriteLine("Loop Started");
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < 30000; i++)
{
stringBuilder.Append("DotNet Tutorials");
}
stopwatch.Stop();
Console.WriteLine("Loop Ended");
Console.WriteLine("Loop Exceution Time in MS :" + stopwatch.ElapsedMilliseconds);
Console.ReadKey();
}
}
}
خروجی :

همانطور که در خروجی بالا مشاهده می کنید، برای الحاق رشته ها فقط 1 میلی ثانیه طول کشید. این به این دلیل است که هر بار که حلقه for اجرا می شود، اشیاء تازه ای ایجاد نمی شود، بلکه از همان مکان حافظه استفاده می کند، یعنی همان شی قدیمی که عملکرد برنامه را به شدت بهبود می بخشد.
دلیل طراحی String ها به صورت immutable :
اکنون سوال اینجاست که چرا رشته ها را در سی شارپ به صورت Immutable ساخته اند. آنها Strings را به عنوان Immutable برای Thread Safety بودن ساخته اند. به موقعیتی فکر کنید که در آن رشتههای زیادی دارید و همه رشتهها میخواهند همان شی رشته را مانند تصویر زیر دستکاری کنند. اگر رشته ها قابل تغییر باشند، در این صورت ما با مشکلات Thread Safety مواجه هستیم.
در پست های بعدی در مورد مباحث چند نخی (Multithreading) و Thread Safety توضیح خواهیم داد.
2 Comments
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
سلام
مثالتان برای اثبات immutable بودن string در C# درست نیست
1- سربار ناشی از ساخت و تبدیل GUID رو نادیده گرفتید
2- زمان اجرا به پارامترهای مختلفی بستگی دارد که لحاظ نشده
3-گفتید همین کار و با int انجام بدید وضعیت فرق میکند ،این کار رو دقیقا با int نمیتونید انجام بدید به، نمیتونید GUID رو در int ذخیره کنید
4- در مثال بعدی ساخت و تبدیل GUID را حذف کردید و رشته ثابت رو هر بار مقدار دادید سربار دو مثال قابل مقایسه نیست
در اینکه رشته ها در C# تغییر ناپذیره حرفی نیست ولی مثال شما اثبات این موضوع نیست
موفق باشید
سلام از توجه و دقتی که به خرج دادین بسیار سپاسگزارم.
نکته اول در مورد مثال int این می باشد که مقدار int را هر بار با مقدار جدید جایگزین کرده و مثال را اجرا کنید.
نکته دوم اینکه در مورد guid کاملا درست می فرمایین اما می توانین همان مثال را به صورت زیر تست کرده و نتیجه را مقایسه کنید.
; str = “test” + i