Введение в ООП с примерами на C#. Часть пятая. Всё о модификаторах доступа

17201

Рассказывает Akhil Mittal

На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

В прошлых статьях серии “Введение в ООП” мы рассматривали полиморфизм (а также нюансы использования его на практике), наследование и абстрактные классы. В этой части я постараюсь раскрыть все тонкости использования модификаторов доступа, которые знаю сам. Продолжаем погружаться в ООП!

Что такое модификаторы доступа?

Давайте в этот раз возьмём определение из Википедии (в русской Википедии статьи access modifiers нет, поэтому здесь приводим свой перевод — прим. перев.):

Модификаторы public, private, protected

Каждый раз, когда мы создаём класс, мы хотим иметь возможность определять, кто и откуда может взаимодействовать с его членами. Иными словами, нам иногда нужно ограничивать доступ к некоторым членам класса. Есть одно простое правило — члены одного класса всегда имеют доступ друг к другу. Если же говорить про доступ извне, то стоит запомнить, что модификатор доступа по умолчанию — private, т.е. все члены класса доступны только изнутри него самого.

Традиционно сразу переходим к практике. Давайте попробуем выполнить следующий код:

			using System;

namespace AccessModifiers
{
    class Modifiers
    {
        static void AAA()
        {
            Console.WriteLine("Modifiers AAA");
        }

        public static void BBB()
        {
            Console.WriteLine("Modifiers BBB");
            AAA();
        }
    }

     class Program
    {
        static void Main(string[] args)
        {
            Modifiers.BBB();
        }
    }
}
		

Результатом выполнения этого кода будет:

BBB() отмечен как public, соответственно его можно вызывать откуда угодно. Метод AAA() же никак не отмечен, значит, он является приватным. Однако для члена того же класса (ведь AAA() и BBB() принадлежат одному классу, верно?) это не имеет никакого значения.

Теперь попробуем получить доступ к AAA() напрямую:

			class Program
    {
        static void Main(string[] args)
        {
            Modifiers.AAA();
            Console.ReadKey();
        }
    }
		

Вывод:

Для внешних вызовов модификатор private — непреодолимая преграда. То же самое можно сказать и о модификаторе protected.

Модификаторы доступа и наследование

Снова попробуем выполнить код:

			class ModifiersBase
    {
        static void AAA()
        {
            Console.WriteLine("ModifiersBase AAA");
        }
        public static void BBB()
        {
            Console.WriteLine("ModifiersBase BBB");
        }
        protected static void CCC()
        {
            Console.WriteLine("ModifiersBase CCC");
        }
    }

class ModifiersDerived:ModifiersBase
{
    public static void XXX()
    {
        AAA();
        BBB();
        CCC();
    }
}

class Program
{
    static void Main(string[] args)
    {
        ModifiersDerived.XXX();
        Console.ReadKey();
    }
}
		

Запускаем код и видим…

Приватные члены недоступны даже дочерним классам. Публичные члены доступны всем, это понятно. Модификатор же protected по сути и обозначает, что член доступен только дочерним классам — вызов CCC() в примере выше не вызывает никаких ошибок.

Модификатор Internal для классов

Давайте рассмотрим следующий сценарий: мы создаём в новой библиотеке классов (назовём её AccessModifiersLibrary) класс ClassA и помечаем его как internal:

			AccessModifiersLibrary.ClassA:

namespace AccessModifiersLibrary
{
    internal class ClassA
    {
    }
}
		

Теперь в созданном ранее файле попробуем выполнить:

			using AccessModifiersLibrary;

namespace AccessModifiers
{
    class Program
    {
        static void Main(string[] args)
        {
            ClassA classA;
        }
    }
}
		

Мы встретили эту ошибку из-за спецификатора доступа internal, который обозначает, что ClassA доступен только внутри AccessModifiersLibrary и ниоткуда больше. Впрочем, если мы уберём этот модификатор, ничего не изменится — internal является спецификатором по умолчанию.

Модификаторы для пространств имён

Давайте попробуем сделать с предыдущим кодом следующее:

			public namespace AccessModifiers
{
    class Program
    {
        static void Main(string[] args)
        {

        }
    }
}
		

Конечно, это не скомпилируется:

Все пространства имён по умолчанию являются публичными, и мы не можем добавить к их объявлению никаких модификаторов, включая ещё один public.

Приватные классы

			namespace AccessModifiers
{
    private class Program
    {
        static void Main(string[] args)
        {

        }
    }
}
		

Если мы попробуем скомпилировать код, приведённый выше, то получим ошибку:

Всё правильно: классы могут быть либо public, либо internal.

Подробнее о модификаторах членов класса

Что будет, если мы захотим назначить члену класса больше одного модификатора доступа?

			namespace AccessModifiers
{
    public class Program
    {
        static void Main(string[] args)
        {
        }

        public private void Method1()
        {

        }
    }
}
		

Будет ошибка компиляции:

А как поведёт себя язык, если мы создадим public метод в internal классе?

			namespace AccessModifiersLibrary
{
    internal class ClassA
    {
        public void MethodClassA(){}
    }
}
		
			using AccessModifiersLibrary;

 namespace AccessModifiers
{
    public class Program
    {
        public static void Main(string[] args)
        {
            ClassA classA = new ClassA();
            classA.MethodClassA();
        }
    }
}
		

Вывод после компиляции:

Как много ошибок… Дело в том, что какими бы модификаторами не обладали члены internal класса, их всё равно нельзя вызвать оттуда, где не виден сам класс. А что будет, если мы попробуем сделать наоборот — вызвать private или internal метод у public класса?

			AccessModifiersLibrary.ClassA:

namespace AccessModifiersLibrary
{
    public class ClassA
    {
        private void MethodClassA(){}
    }
}
		
			using AccessModifiersLibrary;

 namespace AccessModifiers
{
    public class Program
    {
        public static void Main(string[] args)
        {
            ClassA classA = new ClassA();
            classA.MethodClassA();
        }
    }
}
		

Не-а, всё равно не работает. А если изменим модификатор метода на internal?

			AccessModifiersLibrary.ClassA:

namespace AccessModifiersLibrary
{
    public class ClassA
    {
        Internal void MethodClassA(){}
    }
}
		

Увы, так делать тоже нельзя.

Модификатор protected internal

			namespace AccessModifiersLibrary
{
    public class ClassA
    {
        protected internal void MethodClassA()
        {

        }
    }

    public class ClassB:ClassA
    {
        protected internal void MethodClassB()
        {
            MethodClassA();
        }
    }

    public class ClassC
    {
        public void MethodClassC()
        {
            ClassA classA=new ClassA();
            classA.MethodClassA();
        }
    }
}
		
			using AccessModifiersLibrary;

 namespace AccessModifiers
{
    public class Program
    {
        public static void Main(string[] args)
        {
            ClassC classC=new ClassC();
            classC.MethodClassC();
        }
    }
}
		

Этот код компилируется без ошибок. Модификатор internal proteted (как не слишком сложно догадаться) даёт понять, что метод доступен как для вызовов из того же файла, в котором он объявлен, так и для вызовов из дочерних классов.

Protected поля

Здесь всё будет немного сложнее. Давайте напишем следующий код:

			namespace AccessModifiers
{
    class AAA
    {
        protected int a;
        void MethodAAA(AAA aaa,BBB bbb)
        {
            aaa.a = 100;
            bbb.a = 200;
        }
    }
     class BBB:AAA
     {
         void MethodBBB(AAA aaa, BBB bbb)
         {
             aaa.a = 100;
             bbb.a = 200;
         }
     }
    public class Program
    {
        public static void Main(string[] args)
        {
        }
    }
}
		

Если мы его запустим, то получим ошибку:

Совершенно неочевидно, правда? Компилятор ругается на строчку aaa.a = 100 из метода MethodBBB. Почему никаких ошибок не вызывает метод MethodAAA понять достаточно просто — поле a объявлено в том же файле, в том же классе, в котором к нему и происходит обращение, это не может быть ошибкой. Почему в классе BBB доступен член bbb.a тоже понятно — модификатор protected прямо разрешает использовать члены родительского класса в дочернем как свои. Почему же вызов aaa.a = 100 из метода MethodBBB под запретом? Пожалуй, это стоит просто запомнить.

(От редакции) Скорее всего, это сделано, чтобы нельзя было делать следующим образом:

			class AAA{
    protected int a = VALUE;
}
class BBB:AAA{
    public static int getAllTheShit(AAA aaa){
        return aaa.a;
    }
}

//....
public static void Main(string[] args)
{
    theSecret = new AAA();
    theSecret.a /// error
    BBB.getAllTheShit(theSecret) /// azazaz vzlomali

}
		

Приоритет модификаторов

			namespace AccessModifiers
{
    class AAA
    {

    }
    public class BBB:AAA
     {

     }
    public class Program
    {
        public static void Main(string[] args)
        {
        }
    }
}
		

К дочернему классу не может быть большего доступа, чем к родительскому. Как вы понимаете, public предоставляет гораздо больший доступ, чем модификатор по умолчанию internal. Причём нельзя делать даже так:

			namespace AccessModifiers
{
    class AAA
    {

    }
    public class BBB
     {
        public AAA MethodB()
        {
            AAA aaa= new AAA();
            return aaa;
        }
     }
    public class Program
    {
        public static void Main(string[] args)
        {
        }
    }
}
		

Или так:

			namespace AccessModifiers
{
    class AAA
    {

    }
    public class BBB
    {
        public AAA aaa;
    }
    public class Program
    {
        public static void Main(string[] args)
        {
        }
    }
}
		

Подведём итоги:

  • Модификатор доступа по умолчанию для членов класса — private;
  • Модификатор доступа internal значит, что доступ разрешён только из того же файла;
  • У пространств имён нет и не может быть модификаторов доступа (можно считать, что они все public);
  • Классы могут иметь только два модификатора доступа — internal (по умолчанию) и public
  • Модификатор protected internal значит, что доступ есть как из того же файла, так и из дочерних классов
  • Родительский класс не может быть менее доступен, чем дочерний
  • Возвращаемое значение метода не может быть менее доступно, чем сам метод
  • Поле не может быть более доступно, чем его тип

Работу с константами и sealed классами (которая тоже осуществляется за счёт модификаторов доступа) мы разберём в следующей статье.

17201