27-35 METONIT C#

27.  Модификаторы доступа

public class State
{
    // все равно, что private int defaultVar;
    int defaultVar;
    // поле доступно только из текущего класса
    private int privateVar;
    // доступно из текущего класса и производных классов, которые определены в этом же проекте
    protected private int protectedPrivateVar;
    // доступно из текущего класса и производных классов
    protected int protectedVar;
    // доступно в любом месте текущего проекта
    internal int internalVar;
    // доступно в любом месте текущего проекта и из классов-наследников в других проектах
    protected internal int protectedInternalVar;
    // доступно в любом месте программы, а также для других программ и сборок
    public int publicVar;
    // по умолчанию имеет модификатор private
    void defaultMethod() => Console.WriteLine($"defaultVar = {defaultVar}");
    // метод доступен только из текущего класса
    private void privateMethod() => Console.WriteLine($"privateVar = {privateVar}");
    // доступен из текущего класса и производных классов, которые определены в этом же проекте
    protected private void protectedPrivateMethod() => Console.WriteLine($"protectedPrivateVar = {protectedPrivateVar}");
    // доступен из текущего класса и производных классов
    protected void protectedMethod()=> Console.WriteLine($"protectedVar = {protectedVar}");
    
    // доступен в любом месте текущего проекта
    internal void internalMethod() => Console.WriteLine($"internalVar = {internalVar}");
    
    // доступен в любом месте текущего проекта и из классов-наследников в других проектах
    protected internal void protectedInternalMethod() => Console.WriteLine($"protectedInternalVar = {protectedInternalVar}");
    
    // доступен в любом месте программы, а также для других программ и сборок
    public void publicMethod() => Console.WriteLine($"publicVar = {publicVar}");
    }
class Program
{
static void Main(string[] args)
{
State state1 = new State();
// присвоить значение переменной defaultVar у нас не получится,
// так как она имеет модификатор private и класс Program ее не видит
// И данную строку среда подчеркнет как неправильную
state1.defaultVar = 5; //Ошибка, получить доступ нельзя
// то же самое относится и к переменной privateVar
state1.privateVar = 5; // Ошибка, получить доступ нельзя
// присвоить значение переменной protectedPrivateVar не получится,
// так как класс Program не является классом-наследником класса State
state1.protectedPrivateVar =5; // Ошибка, получить доступ нельзя
// присвоить значение переменной protectedVar тоже не получится,
// так как класс Program не является классом-наследником класса State
state1.protectedVar = 5; // Ошибка, получить доступ нельзя
// переменная internalVar с модификатором internal доступна из любого места текущего проекта
// поэтому спокойно присваиваем ей значение
state1.internalVar = 5;
// переменная protectedInternalVar так же доступна из любого места текущего проекта
state1.protectedInternalVar = 5;
// переменная publicVar общедоступна
state1.publicVar = 5;
}
}

28.  Свойства

Кроме обычных методов в языке C# предусмотрены специальные методы доступа, которые называют свойства. Они обеспечивают простой доступ к полям классов и структур, узнать их значение или выполнить их установку.

Стандартное описание свойства имеет следующий синтаксис:

1
2
3
4
[модификатор_доступа] возвращаемый_тип произвольное_название
{
    // код свойства
}

Например:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person
{
    private string name;
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}

Здесь у нас есть закрытое поле name и есть общедоступное свойство Name. Хотя они имеют практически одинаковое название за исключением регистра, но это не более чем стиль, названия у них могут быть произвольные и не обязательно должны совпадать.

Через это свойство мы можем управлять доступом к переменной name. Стандартное определение свойства содержит блоки get и set. В блоке get мы возвращаем значение поля, а в блоке set устанавливаем. Параметр value представляет передаваемое значение.

Мы можем использовать данное свойство следующим образом:

1
2
3
4
5
6
7
8
Person p = new Person();
// Устанавливаем свойство - срабатывает блок Set
// значение "Tom" и есть передаваемое в свойство value
p.Name = "Tom";
// Получаем значение свойства и присваиваем его переменной - срабатывает блок Get
string personName = p.Name;

Возможно, может возникнуть вопрос, зачем нужны свойства, если мы можем в данной ситуации обходиться обычными полями класса? Но свойства позволяют вложить дополнительную логику, которая может быть необходима, например, при присвоении переменной класса какого-либо значения. Например, нам надо установить проверку по возрасту:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person
{
    private int age;
    public int Age
    {
        set
        {
            if (value < 18)
            {
                Console.WriteLine("Возраст должен быть больше 17");
            }
            else
            {
                age = value;
            }
        }
        get { return age; }
    }
}

Если бы переменная age была бы публичной, то мы могли бы передать ей извне любое значение, в том числе отрицательное. Свойство же позволяет скрыть данные в объеты и опосредовать к ним доступ.

Блоки set и get не обязательно одновременно должны присутствовать в свойстве. Если свойство определяют только блок get, то такое свойство доступно только для чтеня – мы можем получить его значение, но не установить. И, наоборот, если свойство имеет только блок set, тогда это свойство доступно только для записи – можно только установить значение, но нельзя получить:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person
{
    private string name;
    // свойство только для чтения
    public string Name
    {
        get
        {
            return name;
        }
    }
    private int age;
    // свойство только для записи
    public int Age
    {
        set
        {
            age = value;
        }
    }
}

Хотя в примерах выше свойства определялись в классе, но точно также мы можем определять и использовать свойства в структурах.

Модификаторы доступа

Мы можем применять модификаторы доступа не только ко всему свойству, но и к отдельным блокам – либо get, либо set:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person
{
    private string name;
    public string Name
    {
        get
        {
            return name;
        }
        private set
        {
            name = value;
        }
    }
    public Person(string name)
    {
        Name = name;
    }
}

Теперь закрытый блок set мы сможем использовать только в данном классе – в его методах, свойствах, конструкторе, но никак не в другом классе:

1
2
3
4
5
6
Person p = new Person("Tom");
// Ошибка - set объявлен с модификатором private
//p.Name = "John";
Console.WriteLine(p.Name);

При использовании модификаторов в свойствах следует учитывать ряд ограничений:

  • Модификатор для блока set или get можно установить, если свойство имеет оба блока (и set, и get)
  • Только один блок set или get может иметь модификатор доступа, но не оба сразу
  • Модификатор доступа блока set или get должен быть более ограничивающим, чем модификатор доступа свойства. Например, если свойство имеет модификатор public, то блок set/get может иметь только модификаторы protected internal, internal, protected, private

Автоматические свойства

Свойства управляют доступом к полям класса. Однако что, если у нас с десяток и более полей, то определять каждое поле и писать для него однотипное свойство было бы утомительно. Поэтому в фреймворк .NET были добавлены автоматические свойства. Они имеют сокращенное объявление:

1
2
3
4
5
6
7
8
9
10
11
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
        
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

На самом деле тут также создаются поля для свойств, только их создает не программист в коде, а компилятор автоматически генерирует при компиляции.

В чем преимущество автосвойств, если по сути они просто обращаются к автоматически создаваемой переменной, почему бы напрямую не обратиться к переменной без автосвойств? Дело в том, что в любой момент времени при необходимости мы можем развернуть автосвойство в обычное свойство, добавить в него какую-то определенную логику.

Стоит учитывать, что нельзя создать автоматическое свойство только для записи, как в случае со стандартными свойствами.

Автосвойствам можно присвоить значения по умолчанию (инициализация автосвойств):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person
{
    public string Name { get; set; } = "Tom";
    public int Age { get; set; } = 23;
}
    
class Program
{
    static void Main(string[] args)
    {
        Person person = new Person();
        Console.WriteLine(person.Name); // Tom
        Console.WriteLine(person.Age);  // 23
        
        Console.Read();
    }
}

И если мы не укажем для объекта Person значения свойств Name и Age, то будут действовать значения по умолчанию.

Стоит отметить, что в структурах мы не можем использовать инициализацию автосвойств.

Автосвойства также могут иметь модификаторы доступа:

1
2
3
4
5
6
7
8
class Person
{
    public string Name { private set; get;}
    public Person(string n)
    {
        Name = n;
    }
}

Мы можем убрать блок set и сделать автосвойство доступным только для чтения. В этом случае для хранения значения этого свойства для него неявно будет создаваться поле с модификатором readonly, поэтому следует учитывать, что подобные get-свойства можно установить либо из конструктора класса, как в примере выше, либо при инициализации свойства:

1
2
3
4
class Person
{
    public string Name { get;} = "Tom"
}

Сокращенная запись свойств

Как и методы, мы можем сокращать свойства. Например:

1
2
3
4
5
6
7
class Person
{
    private string name;
    
    // эквивалентно public string Name { get { return name; } }
    public string Name => name;
}

27. СВОЙСТВА

Кроме обычных методов в языке C# предусмотрены специальные методы доступа, которые называют свойства. Они обеспечивают простой доступ к полям классов и структур, узнать их значение или выполнить их установку.

Стандартное описание свойства имеет следующий синтаксис:

1
2
3
4
[модификатор_доступа] возвращаемый_тип произвольное_название
{
    // код свойства
}

Например:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person
{
    private string name;
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
}

Здесь у нас есть закрытое поле name и есть общедоступное свойство Name. Хотя они имеют практически одинаковое название за исключением регистра, но это не более чем стиль, названия у них могут быть произвольные и не обязательно должны совпадать.

Через это свойство мы можем управлять доступом к переменной name. Стандартное определение свойства содержит блоки get и set. В блоке get мы возвращаем значение поля, а в блоке set устанавливаем. Параметр value представляет передаваемое значение.

Мы можем использовать данное свойство следующим образом:

1
2
3
4
5
6
7
8
Person p = new Person();
// Устанавливаем свойство - срабатывает блок Set
// значение "Tom" и есть передаваемое в свойство value
p.Name = "Tom";
// Получаем значение свойства и присваиваем его переменной - срабатывает блок Get
string personName = p.Name;

Возможно, может возникнуть вопрос, зачем нужны свойства, если мы можем в данной ситуации обходиться обычными полями класса? Но свойства позволяют вложить дополнительную логику, которая может быть необходима, например, при присвоении переменной класса какого-либо значения. Например, нам надо установить проверку по возрасту:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Person
{
    private int age;
    public int Age
    {
        set
        {
            if (value < 18)
            {
                Console.WriteLine("Возраст должен быть больше 17");
            }
            else
            {
                age = value;
            }
        }
        get { return age; }
    }
}

Если бы переменная age была бы публичной, то мы могли бы передать ей извне любое значение, в том числе отрицательное. Свойство же позволяет скрыть данные в объеты и опосредовать к ним доступ.

Блоки set и get не обязательно одновременно должны присутствовать в свойстве. Если свойство определяют только блок get, то такое свойство доступно только для чтеня – мы можем получить его значение, но не установить. И, наоборот, если свойство имеет только блок set, тогда это свойство доступно только для записи – можно только установить значение, но нельзя получить:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person
{
    private string name;
    // свойство только для чтения
    public string Name
    {
        get
        {
            return name;
        }
    }
    private int age;
    // свойство только для записи
    public int Age
    {
        set
        {
            age = value;
        }
    }
}

Хотя в примерах выше свойства определялись в классе, но точно также мы можем определять и использовать свойства в структурах.

Модификаторы доступа

Мы можем применять модификаторы доступа не только ко всему свойству, но и к отдельным блокам – либо get, либо set:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person
{
    private string name;
    public string Name
    {
        get
        {
            return name;
        }
        private set
        {
            name = value;
        }
    }
    public Person(string name)
    {
        Name = name;
    }
}

Теперь закрытый блок set мы сможем использовать только в данном классе – в его методах, свойствах, конструкторе, но никак не в другом классе:

1
2
3
4
5
6
Person p = new Person("Tom");
// Ошибка - set объявлен с модификатором private
//p.Name = "John";
Console.WriteLine(p.Name);

При использовании модификаторов в свойствах следует учитывать ряд ограничений:

  • Модификатор для блока set или get можно установить, если свойство имеет оба блока (и set, и get)
  • Только один блок set или get может иметь модификатор доступа, но не оба сразу
  • Модификатор доступа блока set или get должен быть более ограничивающим, чем модификатор доступа свойства. Например, если свойство имеет модификатор public, то блок set/get может иметь только модификаторы protected internal, internal, protected, private

Автоматические свойства

Свойства управляют доступом к полям класса. Однако что, если у нас с десяток и более полей, то определять каждое поле и писать для него однотипное свойство было бы утомительно. Поэтому в фреймворк .NET были добавлены автоматические свойства. Они имеют сокращенное объявление:

1
2
3
4
5
6
7
8
9
10
11
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
        
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

На самом деле тут также создаются поля для свойств, только их создает не программист в коде, а компилятор автоматически генерирует при компиляции.

В чем преимущество автосвойств, если по сути они просто обращаются к автоматически создаваемой переменной, почему бы напрямую не обратиться к переменной без автосвойств? Дело в том, что в любой момент времени при необходимости мы можем развернуть автосвойство в обычное свойство, добавить в него какую-то определенную логику.

Стоит учитывать, что нельзя создать автоматическое свойство только для записи, как в случае со стандартными свойствами.

Автосвойствам можно присвоить значения по умолчанию (инициализация автосвойств):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person
{
    public string Name { get; set; } = "Tom";
    public int Age { get; set; } = 23;
}
    
class Program
{
    static void Main(string[] args)
    {
        Person person = new Person();
        Console.WriteLine(person.Name); // Tom
        Console.WriteLine(person.Age);  // 23
        
        Console.Read();
    }
}

И если мы не укажем для объекта Person значения свойств Name и Age, то будут действовать значения по умолчанию.

Стоит отметить, что в структурах мы не можем использовать инициализацию автосвойств.

Автосвойства также могут иметь модификаторы доступа:

1
2
3
4
5
6
7
8
class Person
{
    public string Name { private set; get;}
    public Person(string n)
    {
        Name = n;
    }
}

Мы можем убрать блок set и сделать автосвойство доступным только для чтения. В этом случае для хранения значения этого свойства для него неявно будет создаваться поле с модификатором readonly, поэтому следует учитывать, что подобные get-свойства можно установить либо из конструктора класса, как в примере выше, либо при инициализации свойства:

1
2
3
4
class Person
{
    public string Name { get;} = "Tom"
}

Сокращенная запись свойств

Как и методы, мы можем сокращать свойства. Например:

1
2
3
4
5
6
7
class Person
{
    private string name;
    
    // эквивалентно public string Name { get { return name; } }
    public string Name => name;
}

28. Перегрузка методов в языке программирования C#

Иногда возникает необходимость создать один и тот же метод, но с разным набором параметров. И в зависимости от имеющихся параметров применять определенную версию метода. Такая возможность еще называется перегрузкой методов (method overloading).

И в языке C# мы можем создавать в классе несколько методов с одним и тем же именем, но разной сигнатурой. Что такое сигнатура? Сигнатура складывается из следующих аспектов:

  • Имя метода
  • Количество параметров
  • Типы параметров
  • Порядок параметров
  • Модификаторы параметров

Но названия параметров в сигнатуру НЕ входят. Например, возьмем следующий метод:

1
2
3
4
public int Sum(int x, int y)
{
    return x + y;
}

У данного метода сигнатура будет выглядеть так: Sum(int, int)

И перегрузка метода как раз заключается в том, что методы имеют разную сигнатуру, в которой совпадает только название метода. То есть методы должны отличаться по:

  • Количеству параметров
  • Типу параметров
  • Порядку параметров
  • Модификаторам параметров

Например, пусть у нас есть следующий класс:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Calculator
{
    public void Add(int a, int b)
    {
        int result = a + b;
        Console.WriteLine($"Result is {result}");
    }
    public void Add(int a, int b, int c)
    {
        int result = a + b + c;
        Console.WriteLine($"Result is {result}");
    }
    public int Add(int a, int b, int c, int d)
    {
        int result = a + b + c + d;
        Console.WriteLine($"Result is {result}");
        return result;
    }
    public void Add(double a, double b)
    {
        double result = a + b;
        Console.WriteLine($"Result is {result}");
    }
}

Здесь представлены четыре разных версии метода Add, то есть определены четыре перегрузки данного метода.

Первые три версии метода отличаются по количеству параметров. Четвертая версия совпадает с первой по количеству параметров, но отличается по их типу. При этом достаточно, чтобы хотя бы один параметр отличался по типу. Поэтому это тоже допустимая перегрузка метода Add.

То есть мы можем представить сигнатуры данных методов следующим образом:

1
2
3
4
Add(int, int)
Add(int, int, int)
Add(int, int, int, int)
Add(double, double)

После определения перегруженных версий мы можем использовать их в программе:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Program
{
    static void Main(string[] args)
    {
        Calculator calc = new Calculator();
        calc.Add(1, 2); // 3
        calc.Add(1, 2, 3); // 6
        calc.Add(1, 2, 3, 4); // 10
        calc.Add(1.4, 2.5); // 3.9
        
        Console.ReadKey();
    }
}

Консольный вывод:

Result is 3
Result is 6
Result is 10
Result is 3.9

Также перегружаемые методы могут отличаться по используемым модификаторам. Например:

1
2
3
4
5
6
7
8
9
10
11
void Increment(ref int val)
{
    val++;
    Console.WriteLine(val);
}
void Increment(int val)
{
    val++;
    Console.WriteLine(val);
}

В данном случае обе версии метода Increment имеют одинаковый набор параметров одинакового типа, однако в первом случае параметр имеет модификатор ref. Поэтому обе версии метода будут корректными перегрузками метода Increment.

А отличие методов по возвращаемому типу или по имени параметров не является основанием для перегрузки. Например, возьмем следующий набор методов:

1
2
3
4
5
6
7
8
9
10
11
12
int Sum(int x, int y)
{
    return x + y;
}
int Sum(int number1, int number2)
{
    return x + y;
}
void Sum(int x, int y)
{
    Console.WriteLine(x + y);
}

Сигнатура у всех этих методов будет совпадать:

1
Sum(int, int)

Поэтому данный набор методов не представляет корректные перегрузки метода Sum и работать не будет.