رشته ها در سی شارپ از اهمیت فراوانی برخوردارند و به همین دلیل دانش کافی در نحوه کار و استفاده از نوع رشته (string) بسیار حائز اهمیت است. این امکانات و بهبودهایی که تیم دات نت مایکروسافت فراهم میکنند برای استفاده بهتر برنامه نویسان و توسعه دهندگان است و چه بهتر که بدانیم که از چه روشی برای طراحی و توسعه برنامه ها استفاده کنیم.
ما در مجموعه آموزشی وایلدکدر این امکان رو فراهم کردیم تا دانشجویان عزیز به بهترین و ساده ترین شکل ممکن بتوانند برنامه نویسی رو یاد بگیرند.
ما قبلا هم گفتیم که نوع رشته (string) یک نوع ارجاعی(reference type) و به صورت immutable است. یعنی قابل تغییر نیستند و با هر تغییر در مقدار رشته یک مکان جدید در حافظه به آن اختصاص داده می شود و مکان قبلی حافظه توسط garbag collector جمع اوری یا حذف خواهد شد.
تکه کد زیر رو ببنید:
string myName = "yaser";
string YourName = "yaser";
string name1 = "y" + "a" + "s" + "er";
string name2 = "y" + "aser";
Console.WriteLine(object.ReferenceEquals(myName, YourName));
Console.WriteLine(object.ReferenceEquals(myName, name1));
Console.WriteLine(object.ReferenceEquals(myName, name2));
خروجی کد :
ما این مقایسه رو بر اساس reference آنها مقایسه کرده ایم که به خانه ای از حافظه اشاره میکند که در آن ذخیره شده است.
در سی شارپ یه بخشی وجود دارد به اسم Intern pool ، که هر بار مقدار رشته ای تعریف می شود یا مقدار آن تغییر میکند بررسی می شود آیا همچین داده مشابهی در حافظه موجود هست یا خیر ؟ البته دقیقا باید یکسان باشند . در این صورت رفرنس متغیر جدید به همان خانه حافظه تخصیص داده خواهد شد.
در زیر ما با استفاده از GCHandle آدرس خانه ی حافظه هر 4 متغیر رو بدست اوردیم .
string myName = "yaser";
string YourName = "yaser";
string name1 = "y" + "a" + "s" + "er";
string name2 = "y" + "aser";
GCHandle gCHandle = GCHandle.Alloc(myName, GCHandleType.Pinned);
GCHandle gCHandle1 = GCHandle.Alloc(YourName, GCHandleType.Pinned);
GCHandle gCHandle2 = GCHandle.Alloc(name1, GCHandleType.Pinned);
GCHandle gCHandle3 = GCHandle.Alloc(name2, GCHandleType.Pinned);
Console.WriteLine("myName :0x" + gCHandle.AddrOfPinnedObject());
Console.WriteLine("YourName:0x" + gCHandle1.AddrOfPinnedObject());
Console.WriteLine("name1 :0x" + gCHandle2.AddrOfPinnedObject());
Console.WriteLine("name2 :0x" + gCHandle3.AddrOfPinnedObject());
Console.ForegroundColor = ConsoleColor.White;
Console.Write("========================================\n");
Console.WriteLine("myName is equal to YourName : "+object.ReferenceEquals(myName, YourName));
Console.WriteLine("myName is equal to name1 : " + object.ReferenceEquals(myName, name1));
Console.WriteLine("myName is equal to name2 : " + object.ReferenceEquals(myName, name2));
خروجی :
در خروجی کد بالا می بینید که ادرس حافظه ای که هر چهار متغیر به آن دسترسی دارند یکسان است. حالا به کد زیر توجه کنید:
string myName = "yaser";
string YourName = "yaser";
string name1 = "y" + "aser";
string name2 = new StringBuilder().Append("y").Append("a").Append("s").Append("er").ToString();
string name3 = string.Intern(name1);
در این تکه کد از متد Intern استفاده کرده ایم و مقدار متغیر name3 را با استفاده از متد Intern با مقدار متغیر name1 مقدار دهی کرده ایم. چه اتفاقی می افتد؟
این متد به عنوان ورودی یک رشته از ما دریافت می کند و یک رشته به عنوان خروجی به ما تحویل می دهد . باعث می شود که رشته جدید (متیر name3) به همان رفرنس یا ادرسی اشاره کند که متغیر name1 اشاره می کند ، در واقع خانه جدیدی از حافظه برای متغیر name3 در نظر گرفته نمی شود و این کار توسط سیستم intern pool انجام می شود.
string myName = "yaser";
string YourName = "yaser";
string name1 = "y" + "aser";
string name2 = new StringBuilder().Append("y").Append("a").Append("s").Append("er").ToString();
string name3 = name2;
string name4 = string.Intern(name2);
در این مثال ما به جز متغیر هایی که به صورت معمول تعریف کرده ایم : myName ، YourName ، name1 متغیر name2 را با استفاده از String Builder ایجاد کرده ایم .
در عین حال دو متغیر دیگر هم با نام های name3 و name4 تعریف کرده ایم و هر دو را برابر متغیر name2 قرار داده ایم. یکی را فقط با مقدار name2 و یکی را با استفاده از متد Intern مقدار دهی کرده ایم. نتیجه را ببینیم!
بله می بینیم که رفرنس name4 که با استفاده از Intern مقدار دهی شده است با 3 متغیر اولیه که تعریف کرده ایم یکی شده با اینکه با استفاده از متغیر name2 مقدار دهی شده است .
حالا می توانیم ببینیم که متد Intern با استفاده از intern pool درون حافظه را کاوش می کند اگه مقدار متغیر جدید در حافظه موجود بود ( باید دقیقا مشابه باشد) ، اشاره گر متغیر جدید را به همان خانه تنظیم می کند و دیگر مکان جدیدی برای متغیر جدید رزرو نمی شود و این عمل نتیجه کار را بهینه و از هدررفت حافظه جلوگیری میکند.
String str1 = "abcd";
// Constructed string, str2, is not explicitly or automatically interned.
String str2 = new StringBuilder().Append("wx").Append("yz").ToString();
Console.WriteLine();
Test(1, str1);
Test(2, str2);
static void Test(int sequence, String str)
{
Console.Write("{0}) The string, '", sequence);
String strInterned = String.IsInterned(str);
if (strInterned == null)
Console.WriteLine("{0}', is not interned.", str);
else
Console.WriteLine("{0}', is interned.", strInterned);
}
این متد بررسی میکند که آیا رشته تعریف شده از intern pool استفاده میکند یا خیر ؟ که در این مثال می بینید که متغیر str2 که با استفاده از StringBuilder ایجاد شده است مقدار false را نشان می دهد . خروجی متد IsInterned در صورتی که رفرنس رشته در intern pool باشد رشته مورد نظر را به ما تحویل می دهد در غیر این صورت مقدار null .