تنفيذ الفئات الخاصة والثابتة والمتداخلة

مكتمل

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

عندما تحتاج إلى إخفاء أو تقليل التعرض لفئة ما، يمكنك استخدام معدلات الوصول للتحكم في رؤية الفئة وأعضائها.

معدلات الوصول

تحتوي جميع الأنواع وأعضاء النوع على مستوى إمكانية وصول ذوي الاحتياجات الخاصة يتحكم في إمكانية الوصول إليها من التعليمات البرمجية الأخرى في التطبيق المحول برمجيا (أو التجميعات الأخرى).

يمكن أن يكون للفئات المعلن عنها مباشرة داخل مساحة الاسم publicأو internal أو وصول file. يتم تعيين الفئات internal الوصول بشكل افتراضي عند عدم تحديد معدل وصول.

استخدم معدلات الوصول التالية لتحديد إمكانية وصول نوع أو عضو عند الإعلان عنه:

  • public: يمكن للتعليمات البرمجية في أي تجميع الوصول إلى هذا النوع أو العضو. يتحكم مستوى إمكانية وصول ذوي الاحتياجات الخاصة للنوع الذي يحتوي على مستوى إمكانية وصول ذوي الاحتياجات الخاصة للأعضاء العامين من النوع.
  • private: يمكن فقط للتعليمات البرمجية المعلن عنها في نفس class أو struct الوصول إلى هذا العضو.
  • protected: يمكن فقط للتعليمات البرمجية في نفس class أو في class مشتق الوصول إلى هذا النوع أو العضو.
  • internal: يمكن فقط للتعليمات البرمجية في نفس التجميع الوصول إلى هذا النوع أو العضو.
  • protected internal: يمكن فقط للتعليمات البرمجية في نفس التجميع أو في فئة مشتقة في تجميع آخر الوصول إلى هذا النوع أو العضو.
  • private protected: يمكن فقط للتعليمات البرمجية في نفس التجميع وفي نفس الفئة أو فئة مشتقة الوصول إلى النوع أو العضو.
  • file: يمكن فقط للتعليمات البرمجية الموجودة في نفس الملف الوصول إلى النوع أو العضو.

يؤدي تعديل record على نوع إلى تجميع المحول البرمجي لأعضاء إضافيين. لا يؤثر معدل record على إمكانية الوصول الافتراضية إما record class أو record struct.

منشئات الفئة الخاصة

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


class NLog
{
    // Private Constructor:
    private NLog() { }

    public static double e = Math.E;  //2.71828...
}

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

تستخدم الدالات الإنشائية الخاصة لمنع إنشاء مثيلات لفئة عند عدم وجود حقول أو أساليب مثيل، مثل الفئة Math في مكتبة .NET، أو عند استدعاء أسلوب للحصول على مثيل لفئة. إذا كانت جميع الأساليب في الفئة ثابتة، ففكر في جعل الفئة كاملة ثابتة.

يوضح المثال التالي فئة باستخدام منشئ خاص.


public class Counter
{
    private Counter() { }

    public static int currentCount;

    public static int IncrementCount()
    {
        return ++currentCount;
    }
}

class TestCounter
{
    static void Main()
    {
        // If you uncomment the following statement, it generates
        // an error because the constructor is inaccessible:
        // Counter aCounter = new Counter();   // Error

        Counter.currentCount = 100;
        Counter.IncrementCount();
        Console.WriteLine("New count: {0}", Counter.currentCount);

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: New count: 101

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


// Counter aCounter = new Counter();   // Error

الفئات الثابتة

الفئة الثابتة هي في الأساس نفس الفئة غير الثابتة، مع اختلاف واحد مهم: لا يمكن إنشاء مثيل لفئة ثابتة. بمعنى آخر، لا يمكنك استخدام عامل تشغيل new لإنشاء متغير من فئة ثابتة. على الرغم من أنه لا يمكنك إنشاء مثيل للفئة، يمكنك الوصول إلى أعضاء فئة ثابتة عن طريق الرجوع إلى اسم الفئة. على سبيل المثال، إذا كان لديك فئة ثابتة تسمى UtilityClass تحتوي على أسلوب ثابت عام يسمى MethodA، يمكنك استدعاء الأسلوب باستخدام مجموعة من أسماء الفئات والأسلوب. يظهر بناء الجملة هذا في المثال التالي:


UtilityClass.MethodA();

يمكن استخدام فئة ثابتة كحاوية للأساليب التي تعمل على معلمات الإدخال ولا يتعين عليها get أو set أي حقول مثيل داخلية.

على سبيل المثال، في مكتبة فئات .NET، تحتوي فئة System.Math الثابتة على أساليب تنفذ العمليات الرياضية. تعمل هذه الأساليب دون أي متطلبات لتخزين أو استرداد البيانات الفريدة لمثيل معين من فئة Math. يمكنك استدعاء أعضاء الفئة عن طريق تحديد اسم الفئة واسم الأسلوب، كما هو موضح في المثال التالي:


double dub = -3.14;
Console.WriteLine(Math.Abs(dub));
Console.WriteLine(Math.Floor(dub));
Console.WriteLine(Math.Round(Math.Abs(dub)));

// Output:
// 3.14
// -4
// 3

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

توفر القائمة التالية الميزات الرئيسية لفئة ثابتة:

  • يحتوي على أعضاء ثابتين فقط.
  • لا يمكن إنشاء مثيل.
  • مغلق.
  • لا يمكن أن يحتوي على منشئات المثيل.

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

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

فيما يلي مثال على فئة ثابتة تحتوي على طريقتين تحولان درجة الحرارة من مئوية إلى فهرنهايت ومن فهرنهايت إلى درجة مئوية:


public static class TemperatureConverter
{
    public static double CelsiusToFahrenheit(string temperatureCelsius)
    {
        // Convert argument to double for calculations.
        double celsius = Double.Parse(temperatureCelsius);

        // Convert Celsius to Fahrenheit.
        double fahrenheit = (celsius * 9 / 5) + 32;

        return fahrenheit;
    }

    public static double FahrenheitToCelsius(string temperatureFahrenheit)
    {
        // Convert argument to double for calculations.
        double fahrenheit = Double.Parse(temperatureFahrenheit);

        // Convert Fahrenheit to Celsius.
        double celsius = (fahrenheit - 32) * 5 / 9;

        return celsius;
    }
}

class TestTemperatureConverter
{
    static void Main()
    {
        Console.WriteLine("Please select the convertor direction");
        Console.WriteLine("1. From Celsius to Fahrenheit.");
        Console.WriteLine("2. From Fahrenheit to Celsius.");
        Console.Write(":");

        string? selection = Console.ReadLine();
        double F, C = 0;

        switch (selection)
        {
            case "1":
                Console.Write("Please enter the Celsius temperature: ");
                F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0");
                Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
                break;

            case "2":
                Console.Write("Please enter the Fahrenheit temperature: ");
                C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0");
                Console.WriteLine("Temperature in Celsius: {0:F2}", C);
                break;

            default:
                Console.WriteLine("Please select a convertor.");
                break;
        }

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Example Output:
    Please select the convertor direction
    1. From Celsius to Fahrenheit.
    2. From Fahrenheit to Celsius.
    :2
    Please enter the Fahrenheit temperature: 20
    Temperature in Celsius: -6.67
    Press any key to exit.
 */

الأعضاء الثابتون

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

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

يمكن تحميل الأساليب الثابتة بشكل زائد ولكن لا يتم تجاوزها، لأنها تنتمي إلى الفئة، وليس إلى أي مثيل للفئة.

على الرغم من أنه لا يمكن الإعلان عن الحقل على أنه static const، فإن حقل const ثابت بشكل أساسي في سلوكه. ينتمي إلى النوع، وليس إلى مثيلات من النوع. لذلك، يمكن الوصول إلى حقول const باستخدام نفس رمز ClassName.MemberName المستخدم للحولات الثابتة. لا يلزم وجود مثيل كائن.

لا يدعم C# المتغيرات المحلية الثابتة (أي المتغيرات التي تم تعريفها في نطاق الأسلوب).

يمكنك تعريف أعضاء الفئة الثابتة باستخدام الكلمة الأساسية static قبل نوع إرجاع العضو، كما هو موضح في المثال التالي:


public class Automobile
{
    public static int NumberOfWheels = 4;

    public static int SizeOfGasTank
    {
        get
        {
            return 15;
        }
    }

    public static void Drive() { }

    public static event EventType? RunOutOfGas;

    // Other nonstatic fields and properties...
}

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


Automobile.Drive();
int i = Automobile.NumberOfWheels;

إذا كانت الفئة تحتوي على حقول ثابتة، فوفر منشئا ثابتا يقوم بتهيئة هذه الحقول عند تحميل الفئة.

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

الفئات المتداخلة

يسمى نوع class المحدد داخل فئة أخرى فئة متداخلة. على سبيل المثال:


public class Container
{
    class Nested
    {
        Nested() { }
    }
}

يتم تعيين إمكانية وصول فئة متداخلة افتراضيا إلى private. وهذا يعني أنه لا يمكن الوصول إلى الفئات المتداخلة إلا من الفئة التي تحتوي عليها. في المثال السابق، يتعذر على الأنواع الخارجية الوصول إلى فئة Nested.

يمكنك تحديد معدل وصول لتعريف إمكانية وصول نوع متداخل، كما يلي:

  • يمكن publicالأنواع المتداخلة من الفئة أو protectedأو internalأو protected internalأو private أو private protected.

ومع ذلك، يؤدي تعريف protectedأو protected internal أو فئة متداخلة private protected داخل فئة مغلقة إلى إنشاء برنامج تحويل برمجي تحذير CS0628، "عضو محمي جديد تم الإعلان عنه في فئة مختومة".

أنذر

يجب أن تدرك أن جعل النوع المتداخل مرئيا خارجيا ينتهك قاعدة جودة التعليمات البرمجية CA1034 "يجب ألا تكون الأنواع المتداخلة مرئية".

يجعل المثال التالي فئة Nested عامة:


public class Container
{
    public class Nested
    {
        Nested() { }
    }
}

يمكن للفئة المتداخلة أو الداخلية الوصول إلى الفئة المضمنة أو الخارجية. للوصول إلى الفئة المضمنة، مررها كوسيطة إلى الدالة الإنشائية للفئة المتداخلة. على سبيل المثال:


public class Container
{
    public class Nested
    {
        private Container? parent;

        public Nested()
        {
        }
        public Nested(Container parent)
        {
            this.parent = parent;
        }
    }
}

الفئة المتداخلة لديها حق الوصول إلى جميع الأعضاء الذين يمكن الوصول إليهم من الفئة المضمنة الخاصة بها. يمكنه الوصول إلى الأعضاء الخاصين والمحميين من الفئة المضمنة، بما في ذلك أي أعضاء محميين موروثين.

في الإعلان السابق، الاسم الكامل Nested الفئة هو Container.Nested. هذا هو الاسم المستخدم لإنشاء مثيل جديد للفئة المتداخلة، كما يلي:


Container.Nested nest = new Container.Nested();