Рассказывает 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();
}
}
}
Результатом выполнения этого кода будет:
Modifiers BBB
Modifiers AAA
BBB()
отмечен как public
, соответственно его можно вызывать откуда угодно. Метод AAA()
же никак не отмечен, значит, он является приватным. Однако для члена того же класса (ведь AAA()
и BBB()
принадлежат одному классу, верно?) это не имеет никакого значения.
Теперь попробуем получить доступ к AAA()
напрямую:
class Program
{
static void Main(string[] args)
{
Modifiers.AAA();
Console.ReadKey();
}
}
Вывод:
‘AccessModifiers.Modifiers.AAA()’ is inaccessible due to its protection level
Для внешних вызовов модификатор 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();
}
}
Запускаем код и видим…
‘AccessModifiers.ModifiersBase.AAA()’ is inaccessible due to its protection level
Приватные члены недоступны даже дочерним классам. Публичные члены доступны всем, это понятно. Модификатор же 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;
}
}
}
Compile time error: ‘AccessModifiersLibrary.ClassA’ is inaccessible due to its protection level
Мы встретили эту ошибку из-за спецификатора доступа internal
, который обозначает, что ClassA
доступен только внутри AccessModifiersLibrary
и ниоткуда больше. Впрочем, если мы уберём этот модификатор, ничего не изменится — internal
является спецификатором по умолчанию.
Модификаторы для пространств имён
Давайте попробуем сделать с предыдущим кодом следующее:
public namespace AccessModifiers
{
class Program
{
static void Main(string[] args)
{
}
}
}
Конечно, это не скомпилируется:
Compile time error: A namespace declaration cannot have modifiers or attributes
Все пространства имён по умолчанию являются публичными, и мы не можем добавить к их объявлению никаких модификаторов, включая ещё один public
.
Приватные классы
namespace AccessModifiers
{
private class Program
{
static void Main(string[] args)
{
}
}
}
Если мы попробуем скомпилировать код, приведённый выше, то получим ошибку:
Compile time error: Elements defined in a namespace cannot be explicitly declared as private, protected, or protected internal
Всё правильно: классы могут быть либо public
, либо internal
.
Подробнее о модификаторах членов класса
Что будет, если мы захотим назначить члену класса больше одного модификатора доступа?
namespace AccessModifiers
{
public class Program
{
static void Main(string[] args)
{
}
public private void Method1()
{
}
}
}
Будет ошибка компиляции:
Compile time error: More than one protection modifier
А как поведёт себя язык, если мы создадим 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();
}
}
}
Вывод после компиляции:
‘AccessModifiersLibrary.ClassA’ is inaccessible due to its protection level
The type ‘AccessModifiersLibrary.ClassA’ has no constructors defined
‘AccessModifiersLibrary.ClassA’ is inaccessible due to its protection level
‘AccessModifiersLibrary.ClassA’ does not contain a definition for ‘MethodClassA’ and
no extension method ‘MethodClassA’ accepting a first argument of type ‘AccessModifiersLibrary.ClassA’
could be found (are you missing a using directive or an assembly reference?)
Как много ошибок… Дело в том, что какими бы модификаторами не обладали члены 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();
}
}
}
‘AccessModifiersLibrary.ClassA’ does not contain a definition
for ‘MethodClassA’ and no extension method ‘MethodClassA’ accepting a first argument
of type ‘AccessModifiersLibrary.ClassA’ could be found (are you missing a using directive or an assembly reference?)
Не-а, всё равно не работает. А если изменим модификатор метода на internal
?
AccessModifiersLibrary.ClassA:
namespace AccessModifiersLibrary
{
public class ClassA
{
Internal void MethodClassA(){}
}
}
‘AccessModifiersLibrary.ClassA’ does not contain a definition for ‘MethodClassA’ and no extension
method ‘MethodClassA’ accepting a first argument of type ‘AccessModifiersLibrary.ClassA’ could be
found (are you missing a using directive or an assembly reference?)
Увы, так делать тоже нельзя.
Модификатор 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)
{
}
}
}
Если мы его запустим, то получим ошибку:
Cannot access protected member ‘AccessModifiers.AAA.a’ via a qualifier of type ‘AccessModifiers.AAA’;
the qualifier must be of type ‘AccessModifiers.BBB’ (or derived from it)
Совершенно неочевидно, правда? Компилятор ругается на строчку 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)
{
}
}
}
Compile time error: Inconsistent accessibility: base class ‘AccessModifiers.AAA’ is less accessible than class ‘AccessModifiers.BBB’
К дочернему классу не может быть большего доступа, чем к родительскому. Как вы понимаете, 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)
{
}
}
}
Inconsistent accessibility: return type ‘AccessModifiers.AAA’ is less accessible than method ‘AccessModifiers.BBB.MethodB()’
Или так:
namespace AccessModifiers
{
class AAA
{
}
public class BBB
{
public AAA aaa;
}
public class Program
{
public static void Main(string[] args)
{
}
}
}
Inconsistent accessibility: field type ‘AccessModifiers.AAA’ is less accessible than field ‘AccessModifiers.BBB.aaa’
Подведём итоги:
- Модификатор доступа по умолчанию для членов класса —
private
; - Модификатор доступа
internal
значит, что доступ разрешён только из того же файла; - У пространств имён нет и не может быть модификаторов доступа (можно считать, что они все
public
); - Классы могут иметь только два модификатора доступа —
internal
(по умолчанию) иpublic
- Модификатор
protected internal
значит, что доступ есть как из того же файла, так и из дочерних классов - Родительский класс не может быть менее доступен, чем дочерний
- Возвращаемое значение метода не может быть менее доступно, чем сам метод
- Поле не может быть более доступно, чем его тип
Работу с константами и sealed
классами (которая тоже осуществляется за счёт модификаторов доступа) мы разберём в следующей статье.