سؤال

بمعنى آخر، هل مؤشر ترابط تنفيذ Singleton هذا آمن:

public class Singleton
{
    private static Singleton instance;

    private Singleton() { }

    static Singleton()
    {
        instance = new Singleton();
    }

    public static Singleton Instance
    {
        get { return instance; }
    }
}
هل كانت مفيدة؟

المحلول

يتم ضمان تشغيل المنشئات الثابتة مرة واحدة فقط لكل مجال تطبيق، قبل إنشاء أي مثيلات للفئة أو الوصول إلى أي أعضاء ثابتين. http://msdn.microsoft.com/en-us/library/aa645612.aspx

التنفيذ الموضح هو مؤشر ترابط آمن للإنشاء الأولي، أي أنه لا يلزم إجراء اختبار قفل أو اختبار فارغ لإنشاء كائن Singleton.ومع ذلك، هذا لا يعني أنه سيتم مزامنة أي استخدام للمثيل.هناك مجموعة متنوعة من الطرق التي يمكن القيام بذلك؛لقد أظهرت واحدة أدناه.

public class Singleton
{
    private static Singleton instance;
    // Added a static mutex for synchronising use of instance.
    private static System.Threading.Mutex mutex;
    private Singleton() { }
    static Singleton()
    {
        instance = new Singleton();
        mutex = new System.Threading.Mutex();
    }

    public static Singleton Acquire()
    {
        mutex.WaitOne();
        return instance;
    }

    // Each call to Acquire() requires a call to Release()
    public static void Release()
    {
        mutex.ReleaseMutex();
    }
}

نصائح أخرى

في حين أن كل هذه الإجابات تعطي نفس الإجابة العامة، هناك تحذير واحد.

تذكر أنه يتم تجميع كافة الاشتقاقات المحتملة لفئة عامة كأنواع فردية.لذا كن حذرًا عند تنفيذ المُنشئات الثابتة للأنواع العامة.

class MyObject<T>
{
    static MyObject() 
    {
       //this code will get executed for each T.
    }
}

يحرر:

هنا هي المظاهرة:

static void Main(string[] args)
{
    var obj = new Foo<object>();
    var obj2 = new Foo<string>();
}

public class Foo<T>
{
    static Foo()
    {
         System.Diagnostics.Debug.WriteLine(String.Format("Hit {0}", typeof(T).ToString()));        
    }
}

في وحدة التحكم:

Hit System.Object
Hit System.String

باستخدام منشئ ثابت في الواقع يكون Threadsafe.يتم ضمان تنفيذ المنشئ الثابت مرة واحدة فقط.

من مواصفات لغة C# http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx:

يتم تنفيذ المُنشئ الثابت للفئة مرة واحدة على الأكثر في مجال تطبيق معين.يتم تشغيل تنفيذ المُنشئ الثابت بواسطة أول الأحداث التالية التي تحدث داخل مجال التطبيق:

  • يتم إنشاء مثيل للفئة.
  • تتم الإشارة إلى أي من الأعضاء الثابتين في الفصل.

لذا، نعم، يمكنك أن تثق في أنه سيتم إنشاء مثيل لمفردتك بشكل صحيح.

لقد أوضح Zooba نقطة ممتازة (وقبلي بـ 15 ثانية أيضًا!) مفادها أن المُنشئ الثابت لن يضمن الوصول المشترك الآمن للخيط إلى المفردة.سيتعين التعامل مع ذلك بطريقة أخرى.

إليك إصدار Cliffnotes من صفحة MSDN أعلاه على c# Singleton:

استخدم النمط التالي، دائمًا، لا يمكنك أن تخطئ:

public sealed class Singleton
{
   private static readonly Singleton instance = new Singleton();

   private Singleton(){}

   public static Singleton Instance
   {
      get 
      {
         return instance; 
      }
   }
}

بالإضافة إلى الميزات المفردة الواضحة، فهو يوفر لك هذين الأمرين مجانًا (فيما يتعلق بالمفرد في لغة c++):

  1. البناء البطيء (أو عدم البناء إذا لم يتم استدعاؤه مطلقًا)
  2. المزامنة

يتم ضمان تشغيل المُنشئين الثابتين مرة واحدة فقط لكل نطاق تطبيق، لذا يجب أن يكون أسلوبك جيدًا.ومع ذلك، فهو لا يختلف وظيفيًا عن الإصدار المضمّن الأكثر إيجازًا:

private static readonly Singleton instance = new Singleton();

تعد سلامة سلسلة المحادثات مشكلة أكبر عندما تقوم بتهيئة الأشياء بتكاسل.

ال مواصفات البنية التحتية للغة المشتركة يضمن أن "مُهيج النوع يجب أن يعمل مرة واحدة تمامًا لأي نوع معين ، ما لم يطلق عليه رمز المستخدم بشكل صريح." (القسم 9.5.3.1.) لذلك ما لم يكن لديك بعض il المذهل على المكالمات السائبة Singleton ::. وخاصية المثيل الخاص بك آمن مؤشر ترابط.

لاحظ أنه إذا وصل منشئ Singleton إلى خاصية المثيل (حتى بشكل غير مباشر)، فستكون خاصية المثيل فارغة.أفضل ما يمكنك فعله هو اكتشاف وقت حدوث ذلك وطرح استثناء، عن طريق التحقق من أن المثيل غير فارغ في ملحق الخاصية.بعد اكتمال المُنشئ الثابت الخاص بك، ستكون خاصية المثيل غير فارغة.

مثل الجواب زومبا يشير إلى أنك ستحتاج إلى جعل Singleton آمنًا للوصول إليه من عدة سلاسل رسائل، أو تنفيذ آلية قفل حول استخدام مثيل Singleton.

سوف يقوم المنشئ الثابت ينهي جري قبل يُسمح لأي موضوع بالوصول إلى الفصل.

    private class InitializerTest
    {
        static private int _x;
        static public string Status()
        {
            return "_x = " + _x;
        }
        static InitializerTest()
        {
            System.Diagnostics.Debug.WriteLine("InitializerTest() starting.");
            _x = 1;
            Thread.Sleep(3000);
            _x = 2;
            System.Diagnostics.Debug.WriteLine("InitializerTest() finished.");
        }
    }

    private void ClassInitializerInThread()
    {
        System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() starting.");
        string status = InitializerTest.Status();
        System.Diagnostics.Debug.WriteLine(Thread.CurrentThread.GetHashCode() + ": ClassInitializerInThread() status = " + status);
    }

    private void classInitializerButton_Click(object sender, EventArgs e)
    {
        new Thread(ClassInitializerInThread).Start();
        new Thread(ClassInitializerInThread).Start();
        new Thread(ClassInitializerInThread).Start();
    }

أنتج الكود أعلاه النتائج أدناه.

10: ClassInitializerInThread() starting.
11: ClassInitializerInThread() starting.
12: ClassInitializerInThread() starting.
InitializerTest() starting.
InitializerTest() finished.
11: ClassInitializerInThread() status = _x = 2
The thread 0x2650 has exited with code 0 (0x0).
10: ClassInitializerInThread() status = _x = 2
The thread 0x1f50 has exited with code 0 (0x0).
12: ClassInitializerInThread() status = _x = 2
The thread 0x73c has exited with code 0 (0x0).

على الرغم من أن المنشئ الثابت استغرق وقتًا طويلاً للتشغيل، إلا أن الخيوط الأخرى توقفت وانتظرت.تقرأ كافة سلاسل الرسائل قيمة _x المعينة في الجزء السفلي من المُنشئ الثابت.

فقط لأكون متحذلقًا، لكن لا يوجد شيء اسمه مُنشئ ثابت، بل مُهيئات من النوع الثابت، هنا صغيرة عرض توضيحي لتبعية المنشئ الثابت الدوري الذي يوضح هذه النقطة.

يضمن المنشئ الثابت أن يكون آمنًا للخيط.راجع أيضًا المناقشة حول Singleton في DeveloperZen:http://www.developerzen.com/2007/07/15/whats-wrong-with-this-code-1-discussion/

على الرغم من أن الإجابات الأخرى صحيحة في الغالب، إلا أن هناك تحذيرًا آخر يتعلق بالمنشئات الثابتة.

حسب القسم II.10.5.3.3 السباقات والمآزق التابع ECMA-335 البنية التحتية اللغوية الشائعة

لا يجوز للتهيئة بمفردها إنشاء طريق مسدود ما لم يكن هناك بعض التعليمات البرمجية التي تسمى من نوع التهيئة (بشكل مباشر أو غير مباشر) تستدعي عمليات الحظر بشكل صريح.

ينتج التعليمة البرمجية التالية في حالة توقف تام

using System.Threading;
class MyClass
{
    static void Main() { /* Won’t run... the static constructor deadlocks */  }

    static MyClass()
    {
        Thread thread = new Thread(arg => { });
        thread.Start();
        thread.Join();
    }
}

المؤلف الأصلي هو إيجور أوستروفسكي، راجع منصبه هنا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top