Свойства в C#
Свойство в языке С# – это член класса, который предоставляет механизм доступа к полю класса (чтение и запись). При использовании свойства, мы обращаемся к нему, как к полю класса, но на самом деле вызывается соответствующий неявный метод, называемый аксессором (accessor). Существует два таких метода: get (для получения данных) и set (для записи).
Объявление простого свойства имеет следующую структуру:
Приведем пример использования свойств. Имеется класс Экзамен (Exam), и в нем есть закрытое поле (оценка) rating, которое не может быть ниже 0 и больше 10. Для управления доступом к этому полю будет использовано свойство Rating:
В свойстве Rating реализуются два метода. В теле аксессора get в итоге должно возвращаться либо значение поля, либо другое значение. В примере 10-ти бальная оценка пересчитывается в 4-х балльную: 2,3,4,5. В аксессоре же присутствует неявный параметр value, который содержит значение, присваиваемое свойству, причем полю будет присваиваться целое число от 0 до 10.
Если, например, мы бы просто сделали поле rating открытым и не использовали бы ни методы, ни свойство для доступа, то мы могли бы записать в это поле любое значение, в том числе и некорректное, а так мы можем контролировать чтение и запись.
Для контроля доступа мы могли бы использовать простые методы, но для этого нам бы пришлось реализовать два отдельных метода с разными именами, и при обращении к ним необходимо использовать скобки, что добавляет лишние неудобства.
Свойство также может предоставлять доступ только к чтению поля или только к записи. Если, например, нам необходимо закрыть доступ на запись, мы просто не указываем аксессор set. Пример:
Заметим, что само свойство не определяет место в памяти для хранения поля, и, соответственно, необходимо отдельно объявить поле, доступом к которому будет управлять свойство (например, rating и Rating).
Автоматические свойства
Автоматическим называют свойство, которое, в отличие от обычного свойства, уже определяет место в памяти (создает неявное поле), но при этом не позволяет создавать логику доступа. Структура объявления::
[модификатор доступа] [тип] [имя_свойства]
У аксессоров таких свойств отсутствует тело. Пример использования:
Автоматически реализуемые свойства имеет смысл использовать тогда, когда нет необходимости накладывать какие-либо ограничения на возможные значения неявного поля свойства. Чем же они тогда предпочтительнее полей? Ответ: У таких свойств остается возможность сделать их только на чтение или только на запись. Для этого используется модификатор доступа private, указываемый перед именем аксессора:
public int Rating < private get; set; >// свойство только на запись
или
public int Rating < get; private set; >// свойство только на чтение
Обратите внимание, что значительная часть членов классов являются именно полями. Разберите также пример работы со свойствами.
NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.
Понимание lvalue и rvalue в C и С++
Привет, Хабр! Представляю вашему вниманию перевод статьи Eli Bendersky, Understanding of lvalues and rvalues in C and C++.
От переводчика: предлагаю Вашему вниманию перевод интересной статьи об lvalue и rvalue в языках C/C++. Тема не нова, но знать об этих понятиях никогда не поздно. Статья рассчитана на новичков, либо на программистов переходящих с C (или других языков) на C++. Поэтому будьте готовы к подробному разжёвыванию. Если вам интересно, добро пожаловать под кат
Термины lvalue и rvalue не являются чем-то таким, с чем часто приходится сталкиваться при программировании на C/C++, а при встрече не сразу становится ясным, что именно они означают. Наиболее вероятное место столкнуться с ними — это сообщения компилятора. Например, при компиляции следующего кода компилятором gcc :
Вы уведите нечто следующее:
Согласен, что этот код немного надуманный, и вряд ли Вы будете писать нечто подобное, однако сообщение об ошибке упоминает lvalue — термин, который не так часто увидишь в туториалах по C/C++. Другой пример нагляден при компиляции следующего кода при помощи g++ :
Вы увидите следующую ошибку:
Опять же, в сообщение об ошибке упоминается мистическое rvalue. Что же в C и C++ понимается под lvalue и rvalue? Это и есть тема данной статьи.
Простое определение
Для начала нарочито дадим определения lvalue и rvalue в упрощённой форме. В дальнейшем эти понятия будут рассмотрены под увеличительным стеклом.
lvalue (locator value) представляет собой объект, который занимает идентифицируемое место в памяти (например, имеет адрес).
rvalue определено путём исключения, говоря, что любое выражение является либо lvalue, либо rvalue. Таким образом из определения lvalue следует, что rvalue — это выражение, которое не представляет собой объект, который занимает идентифицируемое место в памяти.
Элементарные примеры
Термины, определённые выше, могут показаться немного нечёткими. Поэтому стоит сразу рассмотреть несколько простых поясняющих примеров. Предположим, мы имеем дело с переменной целого типа:
Оператор присваивания ожидает lvalue с левой стороны, и var является lvalue, потому что это объект с идентифицируемым местом в памяти. С другой стороны, следующие заклинания приведут к ошибкам:
Ни константа 4 , ни выражение var + 1 не являются lvalue
(что автоматически их делает rvalue). Они не lvalue, потому что оба являются временным результатом выражений, которые не имеют определённого места в памяти (то есть они могут находится в каких-нибудь временных регистрах на время вычислений). Таким образом, присваивание в данном случае не несёт в себе никакого семантического смысла. Иными словами — некуда присваивать.
Теперь должно быть понятно, что означает сообщение об ошибке в первом фрагменте кода. foo возвращает временное значение, которое является rvalue. Попытка присваивания является ошибкой. То есть, видя код foo() = 2; , компилятор сообщает, что ожидает lvalue с левой стороны оператора присваивания.
Однако, не все присваивания результату вызова функции ошибочны. Например, использование ссылок в C++ делает это возможным:
Здесь foo возвращает ссылку, которая является lvalue, то есть ей можно придать значение. Вообще, в C++ возможность возвращать lvalue, как результат вызова функции, существенна для реализации некоторых перегруженных операторов. Как пример приведём перегрузку оператора [] в классах, которые реализуют доступ по результатам поиска. Например std::map :
Присваивание mymap[10] работает, потому что неконстантная перегрузка std::map::operator[] возвращает ссылку, которой может быть присвоено значение.
Изменяемые lvalue
Изначально, когда понятие lvalue было введено в C, оно буквально означало «выражение, применимое с левой стороны оператора присваивания». Однако позже, когда ISO C добавило ключевое слово const , это определение нужно было доработать. Действительно:
Таким образом не всем lvalue можно присвоить значение. Те, которым можно, называются изменяемые lvalue (modifiable lvalues). Формально C99 стандарт определяет изменяемые lvalue как:
Преобразования между lvalue и rvalue
Образно говоря, конструкции языка, оперирующие значениями объектов, требуют rvalue в качестве аргументов. Например, бинарный оператор ‘+’ принимает два rvalue в качестве аргументов и возвращает также rvalue:
Как мы уже видели раньше, a и b оба lvalue. Поэтому в третьей строке они подвергаются неявному преобразованию lvalue-в-rvalue. Все lvalue, которые не являются массивом, функцией и не имеют неполный тип, могут быть преобразованы в rvalue.
Что насчёт преобразования в другую сторону? Можно ли преобразовать rvalue в lvalue? Конечно нет! Это бы нарушило суть lvalue, согласно его определению (Отсутствие неявного преобразования означает, что rvalue не могут быть использованы там, где ожидается lvalue).
Это не означает, что lvalue не могут быть получены из rvalue явным способом. Например, унарный оператор ‘*’ (разыменование) принимает rvalue в качестве аргумента, но возвращает lvalue в качестве результата. Рассмотрим следующий верный код:
Обратно, унарный оператор ‘&’ (адрес) принимает lvalue как аргумент и производит rvalue:
Символ «&» играет несколько другую роль в C++ — он позволяет определить ссылочный тип. Его называют «ссылкой на lvalue». Неконстантной ссылке на lvalue не может быть присвоено rvalue, так как это потребовало бы неверное rvalue-в-lvalue преобразование:
Константным ссылкам на lvalue можно присвоить rvalue. Так как они константы, значение не может быть изменено по ссылке и поэтому проблема модификации rvalue просто отсутствует. Это свойство делает возможным одну из основополагающих идиом C++ — допуск значений по константной ссылке в качестве аргументов функций, что позволяет избежать необязательного копирования и создания временных объектов.
CV-специфицированные rvalues
Так что же значит «cv-неспецифицированный»? CV-спецификатор — это термин, используемый для описания const и volatile спецификаторов типа.
Но как всё это связано с rvalue? В языке C rvalue никогда не имеют cv-специфицированных типов. Это свойство lvalue. Однако в C++ классовые rvalue могут быть cv-специфицированным, что не касается встроенных типов вроде int . Рассмотрим пример:
Вторая строчка в функции main вызовет метод foo() const , так как cbar возвращает объект типа const A , который отличен от A . Это как раз то, что имелось в виду в последнем предложении выдержки из стандарта выше. Кстати, заметьте, что возвращаемое cbar значение является rvalue. Это был пример cv-специфицированных rvalue в действии.
Ссылки на rvalue (C++11)
Ссылки на rvalue и сопутствующий концепт семантики переноса являются одним из наиболее мощным инструментом, добавленным в язык C++11. Подробная дискуссия на эту тему выходит за рамки этой скромной статьи (вы можете найти кучу материала, просто погуглив «rvalue references». Вот некоторые ресурсы, которые я нахожу полезными: этот, этот и особенно вот этот), но всё же я хотел бы привести простой пример, потому что считаю, что данная глава является наиболее подходящим место, чтобы продемонстрировать как понимание lvalue и rvalue расширяет наши возможности рассуждать о нетривиальных концепциях языка.
Добрая половина статьи была потрачена на объяснение того, что одним из самых главных различий между lvalue и rvalue является тот факт, что lvalue можно изменять, в то время как rvalue — нет. Что же, C++11 добавляет одну важнейшую характерную особенность в этом различии, разрешая нам иметь ссылки на rvalue и тем самым изменять их в некоторых случаях.
Как пример рассмотрим наипростейшую реализацию динамического массива целых чисел. Давайте посмотрим лишь на методы относящиесе к теме данной главы:
Итак, здесь присутствуют обычные конструктор и деструктор, конструктор копирования и оператор присваивания (это каноническая реализация копирующего оператора присваивания с точки зрения усточивости к исключениям. Используя конструктор копирования и затем не выбрасывающий исключения std::swap , мы можем быть уверены, что не может возникнуть промежуточного состояния с непроинициализированной памятью, если где-то произойдёт исключение). Все они используют функцию логирования, чтобы мы могли понять, когда они на самом деле вызваны.
Давайте запустим простой код, который копирует содержимое v1 в v2 :
И вот, что мы увидим:
Что совершенно логично, так как это точно отражает, что происходит внутри оператора присваивания. Но давайте предположим, что мы хотим присвоить v2 некоторое rvalue:
Хотя здесь я только присваиваю значение свеже созданному вектору, это является одной из демонстраций общего случая, когда некоторое временное rvalue создаётся и присваивается v2 (это может случится например, если функция возвращает вектор). Вот что мы увидим на экране:
Ого! Выглядит очень хлопотно. В частности, здесь потребовалась дополнительная пара вызовов конструктора с деструктором, чтобы создать, а потом удалить временный объект. И это печально, так как внутри копирующего оператора присваивания другой временный объект создаётся и удаляется. Дополнительная работа за зря.
Но, нет! C++11 даёт нам ссылки на rvalue, с помощью которых можно реализовать «семантику переноса», а в частности «переносящий оператор присваивания» (теперь понятно почему я всё время называл operator= копирующим оператором присваивания. В C++11 эта разница становится важной). Давайте добавим другой operator= в IntVec :
Двойной асперсанд — это ссылка на rvalue. Он означает как раз то, что и обещает — даёт ссылку на rvalue, который будет уничтожен после вызова. Мы можем использовать этот факт, чтобы просто «стащить» внутренности rvalue — они ему всё равно не нужны! Вот, что выведется на экран:
Как мы видим, вызывается новый переносящий оператор присваивания, так как rvalue присваивается v2 . Вызовы конструктора и деструктора всё же необходимы для временного объекта, который создаётся через Intvec(33) . Однако другой временный объект внутри оператора присваивания больше не нужен. Оператор просто меняет внутренний буфер rvalue со своим, и таким образом деструктор rvalue удаляет буфер самого объекта, который больше не будет использоваться. Чисто!
Хочу только отметить ещё раз, что этот пример только вершина айсберга семантики переноса и ссылок на rvalue. Как вы можете догадаться, это сложная тема с множеством частных случаев и загадок. Я пытался лишь продемонстрировать очень интересное применение различий между lvalue и rvalue в C++. Компилятор очевидно может их различать и позаботится о вызове правильного конструктора во время компиляции.
Заключение
Можно написать много C++ кода, не задумываясь о разногласиях rvalue и lvalue, опуская их как непонятный жаргон компилятора в сообщениях об ошибках. Однако, как я пытался показать в этой статье, лучшее владение этой темы обеспечит более глубокое понимание определённых конструкций C++, и сделает части стандарта C++ и дискуссии между экспертами языка для вас более доступными.
В стандарте C++11 эта тема является ещё более важной, так как C++11 вводит понятия ссылок на rvalue и семантики переноса. Чтобы действительно понять новые особенности языка, строгое понимание rvalue и lvalue просто необходимо.
Когда использовать ключевое слово Value в C #
Я использую настраиваемый сериализатор на C #, чтобы сериализовать / десериализовать объект со словарем. Однако во время десериализации объект словаря не устанавливался правильно. Смотрите код:
Возвращаемый объект JointDictionary был пуст, однако в потоке отладчика я заметил объект «значение», в котором было содержимое моего словаря. Изменение моего кода на следующее устранило мою проблему:
Я прочитал о ключевом слове «value» и понимаю, что это зарезервированное слово в C # для указания значения, которое клиент пытается использовать для установки объекта. Итак, мой вопрос: почему справочник по JointDictionary не работает, как в моей первой попытке? И как правильно использовать ключевое слово значения?
6 ответов
Ключевое слово value представляет значение, которое передается свойству. Вы всегда должны использовать его в установщике свойств.
В случае, если вы использовали jointDictionary , вы были привязаны к получателю свойства. Это делает
Который, поскольку он обращается к получателю свойства, действительно становится
Если вы думаете о методах get и set , как о любых других функциях, value имеет немного больше смысла.
В основном синтаксический сахар С # для:
Поскольку тип возвращаемого значения в геттере и значение arg в сеттере заранее объявлены в объявлении свойства, это позволяет избежать избыточной информации.
Если вам не нужна переменная частного класса (либо для проверки, либо для уведомлений), существует еще более простой синтаксис:
В этом случае компилятор автоматически генерирует закрытый член и код для получателя и установщика.
Ваша первая попытка была ошибочной, поскольку ссылка на свойство внутри себя вернула бы значение свойства до того, как свойство было установлено.
Вы действительно сделали:
Если бы это сработало, это было бы нелогично, поскольку вы «по существу» создавали бы ситуацию цикла, когда вы пытаетесь присвоить переменной значение переменной, которую вы пытаетесь присвоить.
Короче говоря, «Пришельцы».
Выполняет следующие действия при вызове set :
- jointDictionary читается через get
- get возвращает _jointDictionary
- _jointDictionary установлен на то, что было возвращено
Таким образом, метод set фактически ничего не делает, а _jointDictionary останется со значением, с которого он был запущен (в данном случае, null ).
Первая реализация — рекурсивная.
_jointDictionary установлен в jointDictionary , который вызывает геттер, который возвращает _jointDictionary , который еще не был установлен.
Таким образом, результат пустой.
Как будто ты сделал:
Ключевое слово value позволяет вам получить доступ к значению, передаваемому в установщик, а это именно то, что вам здесь нужно.