نظرسنجی
بیشترین استفاده شما از اینترنت در چه زمینه ای است؟






 
خروجی RSS

String Transitivity - String Interning


29 مهر 1390

csharpیکی از ویژگی های تساوی در ریاضیات، ویژگی تعدی (Transitivity)  می باشد، به عبارتی اگر A=B و B=C باشد، بنابراین A=C نیز خواهد بود. این قانون به صورت کلی در C# یا سایر زبان های برنامه نویسی نیز صدق می کند، اما آیا همیشه و در همه ی موارد اینچنین است؟

به طور مثال کد زیر را در نظر بگیرید:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // false !?

همانطور که مشاهده می کنید با اینکه سه متغیر دارای مقادیر یکسانی می باشند، اما رابطه ی تعدی در مورد آن ها صدق نمی کند و عبارت سوم مقدار false بر می گرداند. جالبه نه؟

اما دلیل چنین رفتاری چیست؟

به صورت کلی در دات نت دو نوع مقایسه برای اشیاء وجود دارد. در نوع اول وقتی دو Reference type مورد قیاس قرار می گیرند، صرف نظر از محتویات هر شی تنها رفرنس آن ها با هم مقایسه می شود(Reference Comparison)،  به عبارتی اگر هر دو شی به یک مکان از حافظه اشاره کنند، آنگاه باهم برابر خواهند بود و در غیر این صورت خیر. نوع دیگر مقایسه که در مورد Value type هایی از جمله int، struct، enum و ... صورت می پذیرد، بر خلاف نوع اول، محتویات اشیاء را مورد قیاس قرار می دهد (Value Comparison).

با تمام این توضیحات، در مورد رشته ها کمی داستان متفاوت است. با اینکه هر شی از کلاس String یک نوع Reference type محسوب می شود، اما مقایسه ی بین آن ها (در صورتی که هر دو شی از نوع string باشند) از نوع Value Comparison خواهد بود، در واقع دو رشته،  کاراکتر به کاراکتر با هم مقایسه شده و نتیجه تعیین می شود.

حال با در نظر گرفتن این موارد، نتایج مقایسه ی دوم و سوم در کد بالا صحیح و منطقی می باشد. در مقایسه ی str1 و str2 چون دو شی از جنس string می باشند، محتویات آن ها با هم مقایسه شده ولی در مقایسه ی سوم به دلیل اینکه obj از نوع object است، تنها رفرنس ها مقایسه می شوند. اما همچنان مقایسه ی اول مبهم است، با اینکه مقایسه ای از نوع Reference Comparison صورت گرفته است، اما جواب بر خلاف مقایسه ی سوم true است!

وجود چنین رفتاری که تقریبا هدف اصلی من از نوشتن این مقاله بود، ناشی از بهینه سازی مصرف حافظه ای است که برای  string ها در نظر گرفته شده است. هنگام اجرای برنامه CLR برای کلیه ی Literal هایی که محتوای یکسانی دارند، تنها یک شی از کلاس string می سازد و از رفرنس آن برای سایر instance ها با همان محتوا استفاده می کند. به عبارتی در مثال بالا str1 به همان مکانی اشاره می کند که obj اشاره دارد. بنابراین در مقایسه ی obj==str1 به دلیل اینکه هر دو آبجکت رفرنس یکسانی دارند، مقدار true برگشت داده می شود. اما چون مقدار str2 در زمان اجرا تعیین می شود، دارای رفرنس متفاوتی می باشد. این روش بهینه سازی String interning نام دارد.

برای کنترل این روند، CLR از جدولی به نام intern pool استفاده می کند به این ترتیب که به ازای هر Literal با محتوای یکتا، تنها یک رفرنس از آن در این جدول نگه داری می شود و زمان اختصاص حافظه برای یک Literal جدید، محتویات آن با مقادیر داخل pool مقایسه شده و در صورتی که رفرنسی از آن موجود باشد، آن رفرنس به instance مورد نظر اختصاص می یابد. برای چک کردن جدول intern pool کلاس String دارای دو متد به نام های Intern و IsInterned به صورت استاتیک می باشد که با دریافت یک پارامتر str، به دنبال رفرنسی از آن در pool می گردد.

در ادامه بد نیست مثال دیگری برای درک بهتر موضوع داشته باشیم:

using System;

public class Example
{
   public static void Main()
   {
      string str1 = "a";
      string str2 = str1 + "b";
      string str3 = str2 + "c";
      string[] strings = { "value", "part1" + "_" + "part2", str3,
                           String.Empty, null };
      foreach (var value in strings) {
         if (value == null) continue;

         bool interned = String.IsInterned(value) != null;
         if (interned)
            Console.WriteLine("'{0}' is in the string intern pool.",
                              value);
         else
            Console.WriteLine("'{0}' is not in the string intern pool.",
                              value);                      
      }
   }
}

    // The example displays the following output:
    //       'value' is in the string intern pool.
    //       'part1_part2' is in the string intern pool.
    //       'abc' is not in the string intern pool.
    //       '' is in the string intern pool.

در این مثال مقادیر موجود در آرایه توسط متد IsInterned چک می شوند که آیا رفرنسی از آن ها در intern pool وجود دارد یا خیر و در نهایت اگر رفرنسی از آن موجود بود، آن رفرنس و در غیر این صورت مقدار null برگشت داده خواهد شد.

بنابراین همانطور که در نتایج مشاهده می کنید رشته ی str3 به دلیل اینکه Literal نیست و مقدار آن در زمان اجرا توسط متغییر دیگری تعیین می شود، در pool قرار نگرفته است، در مقابل رشته ی “part1” + ”_” + ”part2” را داریم که تلفیقی از 3 Literal است و کامپایلر آن را تشخیص داده و رفرنس آن در pool قرار داده شده است.

** لازم به ذکر است که متد Intern بر خلاف متد IsInterned، زمانی که رفرنس رشته ی مورد نظر را در pool پیدا نکند، آن رشته را در pool قرار داده و رفرنس آن را return می کند، در حالی که متد IsInterned در چنین حالتی مقدار null بر می گرداند.

** موضوعی که کمی در این بین نیاز به توضیح دارد، String.Empty است که در نسخه های مختلف دات نت رفتار های مختلفی بروز می دهد. کد زیر را در نظر بگیرید:

object obj = "";
string str1 = "";
string str2 = String.Empty;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // sometimes true, sometimes false?!

در نسخه های 2 و 3 دات نت interning در مورد String.Empty  یا رشته ی خالی اتفاق نمی افتاد و مقایسه ی obj==str2 مقدار false بود، اما در نسخه ی 3.5 و 4 interning انجام می شود و جواب مقایسه true است.

تعداد آراء: 30 رای
یادداشت ها
رضا رحمتی
1390/08/17 - 11:05:00
جل الخالق
marjan
1390/08/19 - 07:04:00
vaghean azaton mamnonam agha emad :04
بیژن
1390/10/01 - 16:53:50
عالی بود
سینا
1390/10/05 - 12:09:00
سلام داداشی گل خودم.چطوری عماد جان خوبی؟خسته نباشی.طراحی جدید سایتت خیلی خفنه ها... کارت درسته. خب تا بعد.
خیلی گلی. یا علی
عماد:
سلام سینا جان، مرسی خوبم، تو خوبی؟ خوشی؟
قربانت لطف داری
ممنون
یادداشت خود را ثبت کنید



کد امنیتی:    =
loader
در حال ارسال - چند لحظه صبر کنید...