پشتیبانی توکار از ایجاد کلاس‌های Singleton از دات نت 4 به بعد
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: دو دقیقه

روش‌های زیادی برای ایجاد یک وهله‌ی Singleton وجود دارند. وهله‌ای که در طول عمر یک برنامه، تنها یکبار ایجاد شده و حفظ می‌شود. برای مثال شاید متداول‌ترین حالت آن که در بسیاری از کدها دیده می‌شود، تعریف یک متغیر استاتیک در کلاس، غیرعمومی تعریف کردن سازنده‌ی کلاس و وهله سازی این فیلد استاتیک در صورت نال بودن آن است:
    public class WrongSingleton
    {
        static WrongSingleton _instance;

        WrongSingleton()
        {
        }

        public static WrongSingleton Instance
        {
            get { return _instance ?? (_instance = new WrongSingleton()); }
        }
    }
هرچند این روش کار می‌کند اما thread-safe نیست. به این معنا که ممکن است دو ترد در آن واحد به بررسی قسمت ?? instance_ بپردازند و چون هنوز نال است، دوبار وهله سازی کلاس، با فراخوانی new WrongSingleton صورت خواهد گرفت و هر ترد در آن لحظه به وهله‌ی متفاوتی دسترسی خواهد داشت.
راه حل‌های زیادی برای رفع این مشکل با اعمال مباحث قفل گذاری تا نکات ریز مربوط به کامپایلر وجود دارند که لیست آن‌ها را در اینجا می‌توانید مطالعه کنید.

از دات نت 4 به بعد با معرفی الگوی Lazy، امکان پیاده سازی lazy thread safe singletons به صورت توکار در دسترس می‌باشد. نمونه‌ای از آن در کدهای IoC Container معروفی به نام StructureMap بکار رفته‌است:
    public class Container
    {
        // ...
    }

    public static class ObjectFactory
    {
        private static readonly Lazy<Container> _containerBuilder =
            new Lazy<Container>(defaultContainer, LazyThreadSafetyMode.ExecutionAndPublication);

        public static Container Container
        {
            get { return _containerBuilder.Value; }
        }

        private static Container defaultContainer()
        {
            return new Container();
        }
    }
در اینجا کلاس ObjectFactory یک وهله از کلاس Container را در اختیار مصرف کننده قرار می‌دهد؛ با این شرایط:
- چون این وهله توسط کلاس Lazy محصور شده‌است، صرفا در اولین بار دسترسی به آن، نمونه سازی خواهد شد. این مورد سبب کاهش مصرف حافظه‌ی برنامه و همچنین بالا رفتن سرعت برپایی اولیه‌ی آن می‌شود. بسیاری از اشیایی که در یک برنامه تعریف می‌شوند، شاید الزاما جهت ارائه راه حلی برای مساله‌ای خاص، مورد استفاده قرار نگیرند. تعریف آن‌ها به صورت Lazy، سربار نمونه سازی الزامی آن‌ها را حذف خواهد کرد و آن‌را به اولین بار استفاده از شیء مورد نظر، به تعویق خواهد انداخت.
- با استفاده از LazyThreadSafetyMode.ExecutionAndPublication، چندین ترد می‌توانند به خاصیت Container دسترسی پیدا کنند، اما تنها یکی از آن‌ها موفق به وهله سازی این کلاس خواهد شد. البته حالت ExecutionAndPublication، حالت پیش فرض است و الزاما نیازی به ذکر آن نیست.

اینبار بازنویسی کلاس ابتدای بحث با توجه به نکات ذکر شده به صورت زیر خواهد بود:
    public sealed class LazySingleton
    {
        private static readonly Lazy<LazySingleton> _instance =
            new Lazy<LazySingleton>(() => new LazySingleton(), LazyThreadSafetyMode.ExecutionAndPublication);

        private LazySingleton()
        {
        }

        public static LazySingleton Instance
        {
            get { return _instance.Value; }
        }
    }
- در آن سازنده‌ی کلاس، خصوصی تعریف شده‌است.
- تنها وهله‌ی در دسترس کلاس، به صورت استاتیک و نمونه سازی کلاس، توسط کلاس Lazy با پارامتر LazyThreadSafetyMode.ExecutionAndPublication انجام می‌شود.
- علت استفاده از lambda در سازنده‌ی کلاس Lazy، امکان دسترسی به اعضای private کلاس، از طریق یک خاصیت static است.
  • #
    ‫۹ سال و ۵ ماه قبل، جمعه ۲۸ فروردین ۱۳۹۴، ساعت ۰۳:۲۹
    آیا برداشت من بشکل زیر صحیح است؟
    در صورتی که بخواهم یک پراپرتی سراسری به این کلاس (LazySingleton)  اضافه کنیم مانند زیر عمل می‌کنم:
    .....
    private int _myVar;
    public int MyProperty
    {
         get { return _myVar; }
         set { _myVar = value; }
    }
    .....

     و نحوه مصرف آن به شکل زیر است؟
    LazySingleton.Instance.MyProperty = 1000;
    OR
    var foo = LazySingleton.Instance.MyProperty;


    • #
      ‫۹ سال و ۵ ماه قبل، جمعه ۲۸ فروردین ۱۳۹۴، ساعت ۰۴:۳۷
      بر طبق این جواب، تعریف پراپرتی بصورت public درون کلاس singleton می‌تواند انجام شود.
  • #
    ‫۷ سال و ۵ ماه قبل، پنجشنبه ۲۴ فروردین ۱۳۹۶، ساعت ۰۲:۱۱
    با استفاده از کلمه کلیدی lock میشه یک سینگلتون دستی رو به صورت thread-safe انجام داد ؟
    اگر نسخه دات نت کمتر از 4 باشه استفاده از قابلیت میسر نیست.
  • #
    ‫۴ سال قبل، چهارشنبه ۲۹ مرداد ۱۳۹۹، ساعت ۱۷:۰۱
    سلام 
    توی یه پیاده سازی Singleton به این روش من نیاز دارم 
    1) از یه سرویس برای فراخوانی داده‌ها به روش تزریق وابستگی استفاده کنم (که از Autofac استفاده می‌کنم)  
    2) و اینکه متدی که داده‌ها رو فراخوانی میکنه Async هست. چطور از اون متد توی این کلاس Singleton استفاده کنم ؟
    • #
      ‫۴ سال قبل، چهارشنبه ۲۹ مرداد ۱۳۹۹، ساعت ۱۷:۲۴
      - مبحثی که در اینجا مطرح شده، مرتبط با حالت‌های عدم استفاده‌ی از سیستم تزریق وابستگی‌ها است. البته می‌توان این نوع Container‌ها را در حالت «service locator»، در همه‌جا استفاده کرد و محدودیتی هم ندارند.
      - اگر از یک سیستم تزریق وابستگی‌ها استفاده می‌کنید، مطلب جاری را فراموش کنید. یک کلاس معمولی را ایجاد کرده و یک اینترفیس را از آن استخراج کنید (مانند همیشه و بسیار عادی). سپس این اینترفیس و کلاس پیاده سازی کننده‌ی آن‌را با «طول عمر» singleton به این IoC Container معرفی کنید (مهم نیست نام آن IoC Container چیست. این روش همه جا کار می‌کند). اکنون چون مدیریت طول عمر این سرویس توسط IoC container مورد استفاده کنترل می‌شود، می‌توانید در سازنده‌ی آن تمام سرویس‌های دیگر را هم تزریق کرده (مانند تمام سرویس‌های دیگر تعریف شده) و استفاده کنید؛ چون وهله سازی و مدیریت طول عمر آن توسط خود Container مدیریت می‌شود.
      - استفاده و یا تعریف متدهای Async در اینجا هیچ تفاوتی با قبل ندارد. همان امضای متدهای Task دار و در صورت نیاز async دار را ارائه دهید.

      یک نکته: تزریق وابستگی‌ها در سازنده‌ی کلاس‌هایی با طول عمر singleton یکسری نکات خاص خودشان را دارند.
      • #
        ‫۴ سال قبل، پنجشنبه ۳۰ مرداد ۱۳۹۹، ساعت ۰۳:۲۳
        خیلی ممنونم
        اطلاعات خوبی بهم دادید
        لینک‌ها رو مطالعه کردم اما با توجه به مسئله و اطلاعاتی که دادید هنوز نمیدونم از چه روشی استفاده کنم
        من اجزاء زیر رو دارم:
        1) یک کلاس Singleton که اطلاعات مسیر (Route)‌های برنامه و مجموعه اطلاعاتی مربوط به اون مسیر رو به صورت <Dictionay<string,string در خودش نگه داری میکنه(PathinfoSingletonService).
        2) یک FilterAttribute که مسیر درخواست کاربر را استخراج کرده و با استفاده از کلاس PathinfoSingletonService بخش 1 ، اطلاعات مورد نیاز رو به Context اضافه میکنه.
        3) لایه‌های Repository و Service که به صورت DI کار میکنن و از نوع Scoped هستن. اطلاعات لایه Service بایستی در بخش 1 یعنی PathinfoSingletonService  استفاده بشه.
        حال مسئله من اینه که
        1) اگر بخش 1 رو به صورت Singleton در DI ثبت کرده و استفاده کنم با مسئله ای که اینجا مطرح کردید چکار کنم چون در این کلاس با طول عمر Singleton بایستی از یک کلاس با طول عمر Scoped استفاده کنم ؟
        2) در FilterAttribute خودم که پیاده سازی کننده ActionFilterAttribute هست چطور کلاس PathinfoSingletonService بخش 1 رو تزریق کنم ؟