Как обращатся к переменным объекта?


14 0

Интересный вопрос поступил в студию:

в методе класса лучше обращаться к полям этого класса или к свойствам(инкапсулирующим эти поля)? Или зависит от ситуации, допустим в свойстве есть какая-то проверка, тогда через свойство. Если нет проверок, то через поле по идее будет работать быстрее.

Был как-то почти такой вопрос - почему, я против прямого доступа к переменным объекта и я отвечал в комментариях. Сегодня решил ответить в отдельной заметке, чтобы потом проще было находить и ссылаться. В вопросе кроется и ответ - доступ к переменным должен осуществляться ТОЛЬКО через свойства. Никогда не должно быть прямого доступа, даже если вы не делаете никаких проверок. Я понимаю, что впадлу писать get/set и для таких случаев в C# есть офигенная конструкция:

public тип Имя { get; set; }

Например, целочисленное свойства Count может быть объявлено так:

public int Count { get; set; }

Просто, быстро и не нужно писать объявления. Но допустим, что ты объявил переменную: int count и обращаешся к ней напрямую. И вдруг ты понял, что нужно добавить проверку в set и тут начинаешь включать рефакторинг или начинаешь срать в коде:

int _count
public int сount 
{ 
	get { return _count }
	set { 
		проверки
		_count = value; 
	}
}

Теперь count - это свойство, но оно написано с маленькой буквы, хотя везде негласное правило именовать свойства с большое, а переменные с маленькой. Срачь небольшой, но все же есть. Можно изначально назвать переменную с большой буквы на случай если вдруг захотите превратить ее в свойство, но это опять же плохая идея.

А вот если бы ты использовал свойство изначально (public int Count { get; set; }), то его достаточно только развернуть.

Выбор между обращением к свойству или напрямую - эстетический. С практической точки зрения можно выкрутится в любой момент. Но зачем срать в своем коде. Просто приучите себя, чтобы все переменные класса были закрытыми и доступ к ним был только через свойства. Это сделает код красивее и чище.

 

Updated

И так мы находимся в одном и том же классе и пытаемся получить доступ к переменным этого класса. Делать это через свойство или переменную ведь они обе доступны, даже если переменная private. Я предпочитаю получать доступ через свойства даже внутри класса. Но бывает и обращаюсь и напрямую к переменной. Это не ошибка, поэтому я и написал свой комментарий где-то здесь:

To be clear - использование переменных напрямую не является ошибкой, это просто не красиво с точки зрения ООП. Использование переменных напрямую извне класса, это уже ошибка. Не компиляции, а ошибка проектирования.

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

Wheel wheel; // можно задать значение по умолчанию здесь, но если оно простое и нет логики
public Wheel Wheel 
{ get { if (wheel == null) wheel = new Wheel('какие-то параметры, которые могут быть другими свойствами'); return wheel;
} set { проверки wheel = value; } }

Если обращаться к колесу через свойство даже внутри класса, то оно никогда не будет нулевым и будет возвращать значение по умолчанию. В классах, которые работают с базами данных, в getter-е я часто добавляю работу с кешем. Если свойство ссылается на какие-то данные в базе, то я их часто кеширую, поэтому опять же доступ должен быть через свойство даже внутри класса. Или если свойство реально ищет данные в базе данных. В методе get можно вытягивать данные и они будут вытягиваться по мере надобности.

Если вы напишите что-то типа:

object wheel;
public int Wheel 
{
 get; set; 
}

При этом будете обращаться к переменной wheel напрямую даже изнутри класса, а потом вдруг решите добавить значение по умолчанию с какими-то расчетами или кеширование, то придется заниматься рефакторингом.

Когда свойства простого типа такие как int, string, bool..., а не объекты, то к ним внутри класса смело обращайтесь напрямую. Случаи, когда нужно будет написать какой-то код в get или set возникают нереально редко. Да и их значения по умолчанию в основном простые данные и их можно задавать так:

int wheel = 0;
public int Wheel {
 get { return wheel; }
 set { wheel = value; }
}

К такому свойству можно обращаться напрямую, если вы уверены, что никогда не нужно будет делать ничего особенного в get или set. Но я стараюсь почти всегда обращаться через свойства даже внутри класса, даже к целочисленным переменным. Просто приучил себя к этому. И когда возникает этот редкий случай, что нужно добавить проверку или изменить какую-то логику свойства, у меня не возникает проблем.

Мой личный опыт показывает, что переменная в классе становится просто хранилищем данных, а доступ лучше осуществлять через свойство, даже внутри класса. Подчеркиваю - лучше, потому что это не является ошибкой. К простым переменным я часто (хотя нет, не часто, но бывает, чаще все же использую свойства) обращаюсь напрямую, поэтому что это можно. Внутри класса по ООП мы можем делать что угодно. Для внешнего мира класс должен быть черным ящиком. 


Понравилось? Кликни Лайк, чтобы я знал, какой контент более интересен читателям. Заметку пока еще никто не лайкал и ты можешь быть первым


Комментарии

_fc

14 Сентября 2012

Ну не скажи, те кто проектировали cocoa и её touch версию с этим были не согласны, а так же со стандартной системой исключений - и это самый приятный и удобный фреймворк с которым я работал.
Хотя в objective-c для таких случаев можно запретить писать/читать переменную вне класса, чего в других языках с ООП не хватает.


Михаил Фленов

14 Сентября 2012

У objective-C для доступа к переменным так же создаютя getter и setter. Если ты не хочешь писать их, то может сказать synthesize для своей переменной и тогда компилятор создаст за тебя. Если cocoa обращаются как-то к переменным напрямую, то это их проблемы и не значит, что нужно поступать так же.


Михаил Фленов

14 Сентября 2012

To be clear - использование переменных напрямую не является ошибкой, это просто не красиво с точки зрения ООП.


Zergatul

14 Сентября 2012

По поводу "Если нет проверок, то через поле по идее будет работать быстрее." это не всегда верно. Есть такое понятие как инлайн подстановка мелкий функций JIT-компилятором при работе без дебага, так что вполне вероятно однострочные get/set функции JIT заменит на прямой доступ к полям.


klamm

14 Сентября 2012

Спасибо за ответ!
Я сам всегда обращаюсь через свойства, но часто встречаю обращение напрямую(даже у Вас в Библии C#, стр. 90-91).

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

public double Speed
{
    double speed;
    get { ... }
    set { ... }
}

Кому интересно, ссылка habrahabr .ru/post/116234/


_fc

14 Сентября 2012

Никак не могу от C++ переучится, где getter/setter нужно вызывать явно. Постоянно кажется, что доступ идёт напрямую. В C++ это как раз далеко не всегда красиво, особенно если работавший над кодом до программист не заботился о понятности сего действа - это превращается в пытку, ну а если язык полностью объектно ориентированный как C# - согласен, обязательно надо.


Михаил Фленов

14 Сентября 2012

но часто встречаю обращение напрямую(даже у Вас в Библии C#, стр. 90-91).


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


darthat

15 Сентября 2012

klamm, там вроде бы в Библии на стр. 90-91 идет работа с переменными внутри самого класса, и в этом ничего плохого нет. А в сабже, насколько я понял обсуждается доступ к переменным класса извне, который должен предоставляться только через свойства.

Михаил, а все таки, создание свойства-обертки над переменной .NET снижает как-то  производительность?


klamm

15 Сентября 2012

darthat
Как раз и обсуждается доступ изнутри, т.е. из методов этого же класса, снаружи то понятно, что через свойства.


Михаил Фленов

15 Сентября 2012

Чтобы сказать точно, нужно посмотреть, какой IL код генерирует компилятор, но мне кажется, что производительность не изменится. Компилятор .NET достаточно интеллектуален. Чуть позже, когда будет время и я вернусь за Windows компьютер я проведу экспериментик.


Михаил Фленов

15 Сентября 2012

Писал, писал комментарий, и получилось больше, чем в самой заметке с примерами и объяснениями. Перечитайте заметку начиная со слова updated.


darthat

15 Сентября 2012

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


Zergatul

15 Сентября 2012

Михаил, а все таки, создание свойства-обертки над переменной .NET снижает как-то  производительность?

Поскольку ответ Михаила был не совсем полный, об этом напишу я, в качестве дополнения своего комментария выше.

Вот моя тестовая программа:

    class PropTest
    {
        public int Property { get; set; }
        public int Field;
    }

    class Program
    {
        static void Main(string[] args)
        {
            var obj = new PropTest();
            obj.Property = 3;
            Console.WriteLine(@"""Разогрев"" set-ера");

            var sw = new Stopwatch();
            sw.Start();
            const int max = 1000000000;
            for (int i = 0; i < max; i++)
                obj.Property = i;
            sw.Stop();
            Console.WriteLine("Property: " + sw.ElapsedMilliseconds);

            sw.Reset();
            sw.Start();
            for (int i = 0; i < max; i++)
                obj.Field = i;
            sw.Stop();
            Console.WriteLine("Field: " + sw.ElapsedMilliseconds);

            Console.ReadKey();


Запуск скопилированной Debug-версии:

Property: 5906
Field: 2392


И запуск скомпилированной Release-версии:

Property: 537
Field: 536


Если же смотреть IL-код, то там не видно никаких оптимизаций, так как компилятор просто генерирует однострочные get/set функции. Но когда JIT компилирует IL в машинные команды, он умеет оптимизировать вызовы мелких функций на инлайн подстановки (в Release версии). Что и подтверждает проведенный мной тест.


klamm

16 Сентября 2012

Всё теперь вопрос закрыт, спасибо тестам Zergatul!
Вывод: обращаемся к полям только в get/set свойств, и далее везде используем свойства.


Добавить Комментарий

Еще что-нибудь

Хотите найти еще что-то интересное почитать? Можно попробовать отфильтровать заметки на блоге по категориям.

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне