Типы, переменные, значения, экземпляры и выражения в C#
В языке C# тип (например
Переменная является хранилищем данных. Каждая переменная может содержать значение конкретного типа (экземпляр), которое может меняться во время исполнения программы.
Константы являются переменными, значения которых неизменны и определены уже на этапе компиляции. Они так же хранят в себе значения (экземпляры) конкретных типов.
Выражения возвращают конкретные значения по итогу их успешного исполнения. У выражений так же есть конкретный тип.
В языке C# доступны не только большое кол-во встроенных типов (например
💬 Вот такое на первый взгляд необычное поведение связано с особенностью констант в языке C#. Если вас оно удивляет, то по ссылке вы найдёте ответ на вопрос, почему так происходит.
#data_types
В языке C# тип (например
int
) определяет каким будет значение (42
) хранящееся в переменной. Переменная является хранилищем данных. Каждая переменная может содержать значение конкретного типа (экземпляр), которое может меняться во время исполнения программы.
Константы являются переменными, значения которых неизменны и определены уже на этапе компиляции. Они так же хранят в себе значения (экземпляры) конкретных типов.
Выражения возвращают конкретные значения по итогу их успешного исполнения. У выражений так же есть конкретный тип.
В языке C# доступны не только большое кол-во встроенных типов (например
int
, float
), но и конструкции, позволяющие описывать свои собственные типы (class
, enum
, struct
).💬 Вот такое на первый взгляд необычное поведение связано с особенностью констант в языке C#. Если вас оно удивляет, то по ссылке вы найдёте ответ на вопрос, почему так происходит.
#data_types
Telegraph
static vs const
There are situations where a const and a non-const have different semantics. For example: const int y = 42; static void Main() { short x = 42; Console.WriteLine(x.Equals(y)); } prints out True, whereas: static readonly int y = 42; static void Main() { short…
Value и reference типы в языке C#
Значимые и ссылочные типы являются по праву одними из базисов языка C#, а вопрос об их отличиях, как следствие, одним из самых избитых на собеседованиях, поэтому сегодня я постараюсь ответить на него со всеми подробностями:
Значимые типы:
🔸 Унаследованы от System.ValueType (которые, в свою очередь, от System.Object)
🔸 Переменная хранит непосредственно значение, а в случае присваивания его копию
🔸 В качестве аргументов передаются по значению (копируются)
🔸 Не очищаются сборщиком мусора, не имеют деструктор и финализатор
🔸 Объявляются как структура (struct) или перечисление (enum)
🔸 Объявлены с модификатором sealed, а значит не могут выступать базовыми типами для наследников
Ссылочные типы:
🔸 Унаследованы от System.Object или иного ссылочного типа
🔸 Память под экземпляры выделяется в управляемой куче (managed heap)
🔸 Переменная хранит адрес (указатель) на участок памяти в управляемой куче, где уже хранится непосредственно значение (или null)
🔸 В качестве аргументов передаются по ссылке (указатель)
🔸 Очищаются сборщиком мусора
🔸 Объявляются как класс (class), делегат (delegate) или интерфейс (interface)
🔸 Поддерживают наследование
Одной из лучших и наиболее ёмких, на мой взгляд, статей по этой теме является Memory in .NET - what goes where от Jon Skeet'а.
💬 Я намеренно не стал приводить информацию о том, что значимые типы хранятся в стеке (потока выполнения). Дело в том, что это не совсем так. Подробнее про детали реализации value type и механизм выделения памяти для значений можно почитать в статье Eric Lipert'а: The Truth About Value Types.
✅ Понравилась заметка? Так поделись и с другими 😉
#data_types
Значимые и ссылочные типы являются по праву одними из базисов языка C#, а вопрос об их отличиях, как следствие, одним из самых избитых на собеседованиях, поэтому сегодня я постараюсь ответить на него со всеми подробностями:
Значимые типы:
🔸 Унаследованы от System.ValueType (которые, в свою очередь, от System.Object)
🔸 Переменная хранит непосредственно значение, а в случае присваивания его копию
🔸 В качестве аргументов передаются по значению (копируются)
🔸 Не очищаются сборщиком мусора, не имеют деструктор и финализатор
🔸 Объявляются как структура (struct) или перечисление (enum)
🔸 Объявлены с модификатором sealed, а значит не могут выступать базовыми типами для наследников
Ссылочные типы:
🔸 Унаследованы от System.Object или иного ссылочного типа
🔸 Память под экземпляры выделяется в управляемой куче (managed heap)
🔸 Переменная хранит адрес (указатель) на участок памяти в управляемой куче, где уже хранится непосредственно значение (или null)
🔸 В качестве аргументов передаются по ссылке (указатель)
🔸 Очищаются сборщиком мусора
🔸 Объявляются как класс (class), делегат (delegate) или интерфейс (interface)
🔸 Поддерживают наследование
Одной из лучших и наиболее ёмких, на мой взгляд, статей по этой теме является Memory in .NET - what goes where от Jon Skeet'а.
💬 Я намеренно не стал приводить информацию о том, что значимые типы хранятся в стеке (потока выполнения). Дело в том, что это не совсем так. Подробнее про детали реализации value type и механизм выделения памяти для значений можно почитать в статье Eric Lipert'а: The Truth About Value Types.
✅ Понравилась заметка? Так поделись и с другими 😉
#data_types
Встроенные типы и C# keywords
Язык C# включает в себя большой набор встроенных (примитивных) типов, которые мы можем использовать при разработке наших программ.
Каждый их них имеет своё ключевое слово (keyword), которое соответствует встроенному типу платформы .NET. Другими словами, keyword является лишь сокращением или синонимом (alias) для конкретного типа (реализация которого находится в библиотеке BCL - base class library).
Далее представлены все доступные нам встроенные типы в формате C# тип - .NET тип - границы значений:
🔸 bool – System.Boolean – 1 byte (true|false)
🔸 byte – System.Byte – 1 byte (0 to 255)
🔸 sbyte – System.SByte – 1 byte (-128 to 127)
🔸 short – System.Int16 – 2 bytes (-32,768 to 32,767)
🔸 ushort – System.UInt16 – 2 bytes (0 to 65,535)
🔸 int – System.Int32 – 4 bytes (-2,147,483,648 to 2,147,483,647)
🔸 uint – System.UInt32 – 4 bytes (0 to 4,294,967,295)
🔸 long – System.Int64 – 8 bytes (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807)
🔸 ulong – System.UInt64 – 8 bytes (0 to 18,446,744,073,709,551,615)
🔸 float – System.Single – 4 bytes (±1.5e−45 to ±3.4e38, 7 digit precision)
🔸 double – System.Double – 8 bytes (±5.0e−324 to ±1.7e308, 15-16 digit precision)
🔸 decimal – System.Decimal – 16 bytes (±1.0 × 10−28 to ±7.9 × 1028, 28-29 digit precision)
🔸 char – System.Char – 2 bytes (U+0000 to U+ffff, UTF16 Unicode character)
🔸 object - System.Object
Подробнее: Built-in types table - C# Reference.
#data_types
Язык C# включает в себя большой набор встроенных (примитивных) типов, которые мы можем использовать при разработке наших программ.
Каждый их них имеет своё ключевое слово (keyword), которое соответствует встроенному типу платформы .NET. Другими словами, keyword является лишь сокращением или синонимом (alias) для конкретного типа (реализация которого находится в библиотеке BCL - base class library).
Далее представлены все доступные нам встроенные типы в формате C# тип - .NET тип - границы значений:
🔸 bool – System.Boolean – 1 byte (true|false)
🔸 byte – System.Byte – 1 byte (0 to 255)
🔸 sbyte – System.SByte – 1 byte (-128 to 127)
🔸 short – System.Int16 – 2 bytes (-32,768 to 32,767)
🔸 ushort – System.UInt16 – 2 bytes (0 to 65,535)
🔸 int – System.Int32 – 4 bytes (-2,147,483,648 to 2,147,483,647)
🔸 uint – System.UInt32 – 4 bytes (0 to 4,294,967,295)
🔸 long – System.Int64 – 8 bytes (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807)
🔸 ulong – System.UInt64 – 8 bytes (0 to 18,446,744,073,709,551,615)
🔸 float – System.Single – 4 bytes (±1.5e−45 to ±3.4e38, 7 digit precision)
🔸 double – System.Double – 8 bytes (±5.0e−324 to ±1.7e308, 15-16 digit precision)
🔸 decimal – System.Decimal – 16 bytes (±1.0 × 10−28 to ±7.9 × 1028, 28-29 digit precision)
🔸 char – System.Char – 2 bytes (U+0000 to U+ffff, UTF16 Unicode character)
🔸 object - System.Object
Подробнее: Built-in types table - C# Reference.
#data_types
Строковый тип в C#
В дополнении к большому количеству встроенных значимых типов, таким как целочисленные или с плавающей точкой, язык C# так же предоставляет возможность использования встроенного типа для строк.
Тип string является ссылочным и в платформе .NET (или как упоминалось ранее, в библиотеке BCL) соответствует System.String.
Строки содержат последовательность Unicode (UTF16) символов, являясь эквивалентом массиву элементов типа char, а каждый символ (элемент) занимает ровно 2 байта.
Строковый тип является неизменяемым (immutable), другими словами мы не можем изменить значение строки, не пометив на удаление старое и не создав при этом новое.
Так же нам доступен индексатор (оператор обращения по индексу) [] для доступа к конкретным символам строки.
Небольшой пример работы со строками.
Подробнее про строковый тип: Strings in C# and .NET.
💬 Если факт неизменяемости строк довольно известен, то причины этого порой не так очевидны. Исчерпывающий на мой взгляд ответ по этой теме вы сможете найти по следующей ссылке.
#data_types
В дополнении к большому количеству встроенных значимых типов, таким как целочисленные или с плавающей точкой, язык C# так же предоставляет возможность использования встроенного типа для строк.
Тип string является ссылочным и в платформе .NET (или как упоминалось ранее, в библиотеке BCL) соответствует System.String.
Строки содержат последовательность Unicode (UTF16) символов, являясь эквивалентом массиву элементов типа char, а каждый символ (элемент) занимает ровно 2 байта.
Строковый тип является неизменяемым (immutable), другими словами мы не можем изменить значение строки, не пометив на удаление старое и не создав при этом новое.
Так же нам доступен индексатор (оператор обращения по индексу) [] для доступа к конкретным символам строки.
Небольшой пример работы со строками.
Подробнее про строковый тип: Strings in C# and .NET.
💬 Если факт неизменяемости строк довольно известен, то причины этого порой не так очевидны. Исчерпывающий на мой взгляд ответ по этой теме вы сможете найти по следующей ссылке.
#data_types
Тип object в C#
Все типы платформы .NET являются наследниками System.Object, синонимом которого в языке C# является ключевое слово object.
System.Object является базовым не только для встроенных типов, но и тех, которые мы определяем самостоятельно, а значит экземпляр любого типа может быть приведён к object:
Любой класс который мы определяем в C# также автоматически является наследником от object.
Тип object определяет следующий набор доступных для использования экземплярных методов:
🔸 bool Equals(object)
🔸 void Finalize()
🔸 int GetHashCode()
🔸 Type GetType()
🔸 object MemberwiseClone()
🔸 string ToString()
Статические методы включают в себя:
🔸 bool Equals(object, object)
🔸 bool ReferenceEquals(object, object)
Другими словами, каждый из приведённых выше методов унаследован и автоматически доступен при разработке.
💬 Знаете ли вы, что в 64-битных платформах Garbage Collector не позволяет выделять объекты, размер которых превышает 2 GB и выбрасывает OutOfMemoryException? Помочь с этим нам может специальный элемент конфигурации: gcAllowVeryLargeObjects.
#data_types
Все типы платформы .NET являются наследниками System.Object, синонимом которого в языке C# является ключевое слово object.
System.Object является базовым не только для встроенных типов, но и тех, которые мы определяем самостоятельно, а значит экземпляр любого типа может быть приведён к object:
string msg = "A string";
int n = 42;
Person me = new Person("John", 33);
// Can assign anything to an object variable
object o = msg;
o = n;
o = me;
Любой класс который мы определяем в C# также автоматически является наследником от object.
Тип object определяет следующий набор доступных для использования экземплярных методов:
🔸 bool Equals(object)
🔸 void Finalize()
🔸 int GetHashCode()
🔸 Type GetType()
🔸 object MemberwiseClone()
🔸 string ToString()
Статические методы включают в себя:
🔸 bool Equals(object, object)
🔸 bool ReferenceEquals(object, object)
Другими словами, каждый из приведённых выше методов унаследован и автоматически доступен при разработке.
💬 Знаете ли вы, что в 64-битных платформах Garbage Collector не позволяет выделять объекты, размер которых превышает 2 GB и выбрасывает OutOfMemoryException? Помочь с этим нам может специальный элемент конфигурации: gcAllowVeryLargeObjects.
#data_types
Конструкторы по умолчанию у значимых типов в C#
Все значимые (value) типы, встроенные в .NET, поддерживают конструкторы по умолчанию (другими словами, конструкторы без параметров) и могут быть проинициализированы с помощью ключевого слова new. Таким образом, мы можем инициализировать экземпляры значимых типов default'ными для них значениями.
Обычно мы объявляем переменные и сразу же инициализируем их с помощью литералов, однако, ничто не мешает использовать не только new, но и ключевое слово default:
🔸 bool type = false
🔸 Numeric types (e.g. int, float) = 0 or 0.0
🔸 char type = single empty character
🔸 DateTime type = 1/1/0001 12:00:00 AM
🔸 string - не значимый тип 😉
На основании замечательной статьи от Jon Skeet - Value types and parameterless constructors стоит также отметить, что:
Операции, которые вызывают parameterless конструктор:
🔸 Использование new int();
🔸 Activator.CreateInstance (обе версии);
Операции, которые не вызывают parameterless конструктор:
🔸 Объявление переменной (неважно какой: локальной, статической, поля);
🔸 Упаковка (boxing);
🔸 Инициализация массива;
🔸 Использование default(T) и new(T) в generic методах;
Больше информации вы сможете найти в уже упомянутой выше статье и по данной ссылке.
#data_types
Все значимые (value) типы, встроенные в .NET, поддерживают конструкторы по умолчанию (другими словами, конструкторы без параметров) и могут быть проинициализированы с помощью ключевого слова new. Таким образом, мы можем инициализировать экземпляры значимых типов default'ными для них значениями.
Обычно мы объявляем переменные и сразу же инициализируем их с помощью литералов, однако, ничто не мешает использовать не только new, но и ключевое слово default:
int i;Далее представлены default значения встроенных типов:
int n1 = 12;
int n2 = new int();
int n3 = default(int);
🔸 bool type = false
🔸 Numeric types (e.g. int, float) = 0 or 0.0
🔸 char type = single empty character
🔸 DateTime type = 1/1/0001 12:00:00 AM
🔸 string - не значимый тип 😉
На основании замечательной статьи от Jon Skeet - Value types and parameterless constructors стоит также отметить, что:
Операции, которые вызывают parameterless конструктор:
🔸 Использование new int();
🔸 Activator.CreateInstance (обе версии);
Операции, которые не вызывают parameterless конструктор:
🔸 Объявление переменной (неважно какой: локальной, статической, поля);
🔸 Упаковка (boxing);
🔸 Инициализация массива;
🔸 Использование default(T) и new(T) в generic методах;
Больше информации вы сможете найти в уже упомянутой выше статье и по данной ссылке.
#data_types
Иерархия типов в C#
Все типы в .NET являются наследниками тех или иных базовых типов (за исключением System.Object, который находится на вершине иерархии).
Наследование является одним из фундаментальных атрибутов объектно-ориентированного программирования. Оно позволяет определить дочерний класс, который использует (наследует), расширяет или изменяет возможности родительского класса. Класс, члены которого наследуются, называется базовым классом. Класс, который наследует члены базового класса, называется производным классом.
C# и .NET поддерживают только одиночное наследование. Это означает, что каждый класс может наследовать члены только одного класса. Но зато поддерживается транзитивное наследование, которое позволяет определить иерархию наследования для набора типов. Другими словами, тип D может наследовать возможности типа C, который в свою очередь наследует от типа B, который наследует от базового класса A. Благодаря транзитивности наследования члены типа A будут доступны для типа D.
Больше информации и примеров вы сможете найти в следующей статье - Наследование в C# и .NET.
#data_types
Все типы в .NET являются наследниками тех или иных базовых типов (за исключением System.Object, который находится на вершине иерархии).
Наследование является одним из фундаментальных атрибутов объектно-ориентированного программирования. Оно позволяет определить дочерний класс, который использует (наследует), расширяет или изменяет возможности родительского класса. Класс, члены которого наследуются, называется базовым классом. Класс, который наследует члены базового класса, называется производным классом.
C# и .NET поддерживают только одиночное наследование. Это означает, что каждый класс может наследовать члены только одного класса. Но зато поддерживается транзитивное наследование, которое позволяет определить иерархию наследования для набора типов. Другими словами, тип D может наследовать возможности типа C, который в свою очередь наследует от типа B, который наследует от базового класса A. Благодаря транзитивности наследования члены типа A будут доступны для типа D.
Больше информации и примеров вы сможете найти в следующей статье - Наследование в C# и .NET.
#data_types
MinValue и MaxValue числовых типов в C#
Ранее мы уже рассматривали встроенные числовые типы. Сегодня хотелось бы рассказать о двух примечательных полях (константах), которые содержит каждый из них- MaxValue и MinValue. Обращаться к ним можно следующим образом:
Для чего они могут нам понадобиться?
В первую очередь, их наличие обусловлено удобством в отсутствии необходимости не только запоминать минимальные и максимальные значения разных типов, но и явно прописывать их в коде. Одно дело, когда мы используем неизменяемые константы, в которых можем быть уверены и совершенно другое, когда эти значения мы пишем сами (защита от ошибок при вводе или случайного изменения).
Во-вторых, это удобно при поиске минимальных и максимальных значений в коллекции. В случае поиска минимального элемента в массиве мы можем проинициализировать переменную, которая будет содержать результат поиска максимальным значением её типа. И уже отталкиваться от него в сравнениях как в следующем примере.
☝️ Насколько мне известно, прироста производительности от использования типов меньшего размера в циклах в C# замечено не было. Однако, при описании структур данных стоит помнить о том, что такие типы как ushort, short и byte умещаются в меньшее количество бит, а значит более предпочтительны с точки зрения performance и потребления памяти (hold on for a second, это не призыв к действию, а всего лишь напоминание 🙂).
В заключение
💬 Ну и напоследок стоит также отметить, что использовать минимальные и максимальные значения в циклах стоит с осторожностью, иначе это может привести к неожиданным последствиям. Примером тому служит следующий код (кстати говоря, это реальный вопрос с одного из собеседований). Вопрос: что выведется на экране? Ответ вы сможете найти ниже на странице 😉
#data_types
Ранее мы уже рассматривали встроенные числовые типы. Сегодня хотелось бы рассказать о двух примечательных полях (константах), которые содержит каждый из них- MaxValue и MinValue. Обращаться к ним можно следующим образом:
byte bMin = byte.MinValue;
// 0
byte bMax = byte.MaxValue;
// 255 (2^8)
char cMin = char.MinValue;
// 0x0000
char cMax = char.MaxValue;
// 0xffff (2^16)
int nMin = int.MinValue;
// -2147483648
int nMax = int.MaxValue;
// 2147483647 (2^32)
long lMin = long.MinValue;
// -9,223,372,036,854,775,808
long lMax = long.MaxValue;
// 9,223,372,036,854,775,807 (2^64)
Для чего они могут нам понадобиться?
В первую очередь, их наличие обусловлено удобством в отсутствии необходимости не только запоминать минимальные и максимальные значения разных типов, но и явно прописывать их в коде. Одно дело, когда мы используем неизменяемые константы, в которых можем быть уверены и совершенно другое, когда эти значения мы пишем сами (защита от ошибок при вводе или случайного изменения).
Во-вторых, это удобно при поиске минимальных и максимальных значений в коллекции. В случае поиска минимального элемента в массиве мы можем проинициализировать переменную, которая будет содержать результат поиска максимальным значением её типа. И уже отталкиваться от него в сравнениях как в следующем примере.
☝️ Насколько мне известно, прироста производительности от использования типов меньшего размера в циклах в C# замечено не было. Однако, при описании структур данных стоит помнить о том, что такие типы как ushort, short и byte умещаются в меньшее количество бит, а значит более предпочтительны с точки зрения performance и потребления памяти (hold on for a second, это не призыв к действию, а всего лишь напоминание 🙂).
В заключение
💬 Ну и напоследок стоит также отметить, что использовать минимальные и максимальные значения в циклах стоит с осторожностью, иначе это может привести к неожиданным последствиям. Примером тому служит следующий код (кстати говоря, это реальный вопрос с одного из собеседований). Вопрос: что выведется на экране? Ответ вы сможете найти ниже на странице 😉
#data_types
TrueString и FalseString в C#
В продолжении темы о доступных полях встроенных типов сегодня хочу рассказать о
Эти поля возвращают "True" и "False" независимо от текущих настроек региона, языка или языка программирования (C# или VB .NET). Аналогичный результат можно получить вызывая метод
Не могу вспомнить примера, когда бы они действительно могли мне пригодиться, однако, могу предположить, что это очередные фиксированные значения, которые могут быть использованы в приложении в качестве неизменяемых контрактов (например, при единообразной работе с базой данных на чтение и запись).
💬 Оказалось, что довольно большое количество разработчиков (больше 90000 просмотров) интересует вопрос: почему
#data_types
В продолжении темы о доступных полях встроенных типов сегодня хочу рассказать о
Boolean.TrueString
и Boolean.FalseString
. Эти поля дают возможность использовать строковое представление соответствующих булевых значений в коде.string trueText = bool.TrueString; // "True"
string falseText = bool.FalseString; // "False"
Эти поля возвращают "True" и "False" независимо от текущих настроек региона, языка или языка программирования (C# или VB .NET). Аналогичный результат можно получить вызывая метод
.ToString()
на соответствующем значении:bool b1 = true;
bool b2 = false;
string true2 = b1.ToString(); // "True"
string false2 = b2.ToString(); // "False"
Не могу вспомнить примера, когда бы они действительно могли мне пригодиться, однако, могу предположить, что это очередные фиксированные значения, которые могут быть использованы в приложении в качестве неизменяемых контрактов (например, при единообразной работе с базой данных на чтение и запись).
💬 Оказалось, что довольно большое количество разработчиков (больше 90000 просмотров) интересует вопрос: почему
Boolean.ToString
возвращает "True" а не "true". Причины, на удивление, есть и их несколько. О них вы сможете прочитать уже самостоятельно 😉#data_types
Создаём объекты с помощью new в C#
Для создания объекта некого типа нам необходимо создать экземпляр этого типа. В случае со значимыми типами нам достаточно присвоить им значение. Для ссылочных же потребуется использовать оператор
Принцип его работы достаточно прост:
🔸 Выделяет память под экземпляр типа и инициализирует все поля объекта значениями по умолчанию;
🔸 Вызывает конструктор типа
🔸 Возвращает ссылку на созданный объект в случае если тип ссылочный либо значение, если тип значимый
Что касается конструкторов, то их у типа может быть несколько и различаться они будут входными аргументами (конструктор не имеющий принимающий аргументов является конструктором по умолчанию). Оператор
Также стоит напомнить, что объявленные переменные ссылочных типов будут содержать значение
Больше информации и примеров вы сможете найти в следующей статье - new operator.
#data_types
Для создания объекта некого типа нам необходимо создать экземпляр этого типа. В случае со значимыми типами нам достаточно присвоить им значение. Для ссылочных же потребуется использовать оператор
new
.Принцип его работы достаточно прост:
🔸 Выделяет память под экземпляр типа и инициализирует все поля объекта значениями по умолчанию;
🔸 Вызывает конструктор типа
🔸 Возвращает ссылку на созданный объект в случае если тип ссылочный либо значение, если тип значимый
Что касается конструкторов, то их у типа может быть несколько и различаться они будут входными аргументами (конструктор не имеющий принимающий аргументов является конструктором по умолчанию). Оператор
new
оставляет за нами право выбора оного.Также стоит напомнить, что объявленные переменные ссылочных типов будут содержать значение
null
до тех пор, пока не будут проинициализированы:// Not instantiated, value is null
Person p1;
// p2 points to new instance of the Person class
// Default constructor, takes no parameters
Person p2 = new Person();
// Construct another Person object using a different constructor,
// which takes Name and Age
Person p3 = new Person("John", 42);
Больше информации и примеров вы сможете найти в следующей статье - new operator.
#data_types
Создание объектов и куча (Heap) в C#
В прошлый раз мы рассмотрели оператор
Объекты .NET размещаются в области памяти, которая называется управляемой кучей (managed heap), откуда они автоматически удаляются сборщиком мусора, когда наступает "определенный момент в будущем" 🙂. Куча представляет собой непрерывную область памяти, поделённую на занятые и свободные области (блоки) различного размера.
При программировании на языке C# мы можем смело полагать, что исполняющая среда .NET будет сама заботиться об управляемой куче без непосредственного нашего с вами вмешательства.
После создания объект будет автоматически удален сборщиком мусора тогда, когда в нем отпадет необходимость. Разумеется, возникает вопрос о том, каким образом сборщик мусора определяет момент, когда в объекте отпадает необходимость? В двух словах на этот вопрос можно ответить так: сборщик мусора удаляет объект из кучи тогда, когда тот становится недостижимым ни в одной части программного кода.
Возвращаясь к примеру, который я уже скидывал ранее теперь мы можем лучше понимать, что происходит в следующей строке:
Оператор
Больше деталей вы сможете найти по ссылке - автоматическое управление памятью.
#data_types
В прошлый раз мы рассмотрели оператор
new
и принцип его работы. Сегодня я хотел бы упомянуть о такой немаловажной детали как куча.Объекты .NET размещаются в области памяти, которая называется управляемой кучей (managed heap), откуда они автоматически удаляются сборщиком мусора, когда наступает "определенный момент в будущем" 🙂. Куча представляет собой непрерывную область памяти, поделённую на занятые и свободные области (блоки) различного размера.
При программировании на языке C# мы можем смело полагать, что исполняющая среда .NET будет сама заботиться об управляемой куче без непосредственного нашего с вами вмешательства.
После создания объект будет автоматически удален сборщиком мусора тогда, когда в нем отпадет необходимость. Разумеется, возникает вопрос о том, каким образом сборщик мусора определяет момент, когда в объекте отпадает необходимость? В двух словах на этот вопрос можно ответить так: сборщик мусора удаляет объект из кучи тогда, когда тот становится недостижимым ни в одной части программного кода.
Возвращаясь к примеру, который я уже скидывал ранее теперь мы можем лучше понимать, что происходит в следующей строке:
class1 cls1 = new class1();
Оператор
new
выделяет память под объект типа class1
и располагает его в Heap. В то же время на стеке выделяется память под переменную cls1
, которая будет хранить ссылку на создаваемый нами объект. И затем ссылка на только что созданный объект уже присваивается в нашу переменную. Вы можете также заметить, что по завершению выполнения метода Method1
его стек очищается, в то время как объект всё так же присутствует в куче, становясь, тем самым, кандидатом для очистки при следующей сборки мусора.Больше деталей вы сможете найти по ссылке - автоматическое управление памятью.
#data_types
Поведение переменных ссылочных типов в C#
Как вы уже знаете, переменные значимых типов хранят в себе непосредственно значение, в то время как переменные ссылочных типов лишь ссылку на объект. В связи с этим нам необходимо помнить о том, что при присваивании значения одной ссылочной переменной другой, обе они в итоге начинают ссылаться на один и тот же объект в куче:
Таким образом, если мы изменим значения полей объекта
Подробности вы сможете найти по ссылке - Six important .NET concepts: Stack, heap, value types, reference types, boxing, and unboxing.
#data_types
Как вы уже знаете, переменные значимых типов хранят в себе непосредственно значение, в то время как переменные ссылочных типов лишь ссылку на объект. В связи с этим нам необходимо помнить о том, что при присваивании значения одной ссылочной переменной другой, обе они в итоге начинают ссылаться на один и тот же объект в куче:
public void Method1() {
cls1 obj = new cls1();
cls1 obj1 = obj;
}
Таким образом, если мы изменим значения полей объекта
obj
, то мы увидим данные изменения и в переменной obj1
. Это подтверждает что обе переменных ссылаются на один и тот же объект.Подробности вы сможете найти по ссылке - Six important .NET concepts: Stack, heap, value types, reference types, boxing, and unboxing.
#data_types
Числовые преобразования в C#
Ранее я уже писал о числовых типах, которые существуют в языке C#. Сегодня же речь пойдёт об их преобразовании.
В тот момент, когда значение одного типа (допустим
Значения значимых типов преобразуются (конвертируются) неявно (implicitly) тогда и только тогда, когда отсутствует вероятность потери точности результата (data loss). Это возможно в том случае, когда тип, к которому мы хотим преобразовать наше значение, хранится в памяти в большем или равном количестве бит, как и исходный.
🔸 implicit преобразования:
Как видно из кода выше, нам не требуется никаких дополнительных действий для преобразования, ведь
🔸 explicit преобразования:
Если мы захотим выполнить аналогичное преобразование в обратном направлении, то компилятор не позволит нам это сделать и сообщит об ошибке на этапе компиляции, как в примере выше.
В таком случае мы будем вынуждены производить преобразования уже явно:
💬 Позвольте познакомить вас со своим тёзкой, который также имеет богатый практический опыт в разработке программного обеспечения и преподает айтишные предметы в ВУЗе. Почитайте https://yangx.top/tobeITmen, чтобы узнать, каково это - быть айтишником 😉
#data_types
Ранее я уже писал о числовых типах, которые существуют в языке C#. Сегодня же речь пойдёт об их преобразовании.
В тот момент, когда значение одного типа (допустим
int
) присваивается переменной другого типа (например long
), выполняется преобразование типов.Значения значимых типов преобразуются (конвертируются) неявно (implicitly) тогда и только тогда, когда отсутствует вероятность потери точности результата (data loss). Это возможно в том случае, когда тип, к которому мы хотим преобразовать наше значение, хранится в памяти в большем или равном количестве бит, как и исходный.
🔸 implicit преобразования:
int i = 12;
long l = i; // Implicit (int to long)
float f = i; // Implicit (int to float)
double d = 4.2f; // Implicit (float to double)
Как видно из кода выше, нам не требуется никаких дополнительных действий для преобразования, ведь
long
и double
располагаются в 64 битах, в то время как int
в 32, а значит потеря точности результата нам не грозит.🔸 explicit преобразования:
long l = 12;
int i = l; // Compiler error
Если мы захотим выполнить аналогичное преобразование в обратном направлении, то компилятор не позволит нам это сделать и сообщит об ошибке на этапе компиляции, как в примере выше.
В таком случае мы будем вынуждены производить преобразования уже явно:
int i = (int)l; // Explicit (long to int)
float f = 4.2f;
i = (int)f; // Explicit (float to int)
double d = 4.2f;
f = (float)d; // Explicit (double to float)
💬 Позвольте познакомить вас со своим тёзкой, который также имеет богатый практический опыт в разработке программного обеспечения и преподает айтишные предметы в ВУЗе. Почитайте https://yangx.top/tobeITmen, чтобы узнать, каково это - быть айтишником 😉
#data_types
Явное приведении значимых типов в C#
Выполняя явные преобразования числовых типов мы можем столкнуться с ситуацией, при которой исходное значение не может корректно отобразиться на требуемый нам тип. В таком случае дальнейшие выполнение приведения осуществляется по следующим правилам:
🔸 Целочисленное к целочисленному - урезание ведущих битов;
🔸
🔸
🔸
🔸
Также не стоит забывать и об операторе
Подробнее с правилами явного приведения вы сможете ознакомиться уже в документации.
💬 На мой взгляд, необходимости заучивать все эти правила нет, однако, помнить - безусловно стоит. Если у вас ещё остались вопросы по преобразованиям или числам с плавающей точкой, то я настоятельно рекомендую вам ознакомиться со следующей темой самостоятельно - Difference between decimal, float and double in .NET?.
#data_types
Выполняя явные преобразования числовых типов мы можем столкнуться с ситуацией, при которой исходное значение не может корректно отобразиться на требуемый нам тип. В таком случае дальнейшие выполнение приведения осуществляется по следующим правилам:
🔸 Целочисленное к целочисленному - урезание ведущих битов;
🔸
Decimal
, float
и double
к целочисленному - обрезание дробной части и ведущих битов;🔸
Double
к float
- округление или Infinity
в случае переполнения;🔸
Float
и double
к decimal
- округление;🔸
Decimal
к float
или double
- потеря точности;Также не стоит забывать и об операторе
checked
, который выбрасывает исключение в случае переполнения.Подробнее с правилами явного приведения вы сможете ознакомиться уже в документации.
💬 На мой взгляд, необходимости заучивать все эти правила нет, однако, помнить - безусловно стоит. Если у вас ещё остались вопросы по преобразованиям или числам с плавающей точкой, то я настоятельно рекомендую вам ознакомиться со следующей темой самостоятельно - Difference between decimal, float and double in .NET?.
#data_types
Статические методы типа System.Char в C#
Тип
Далее я перечислю лишь некоторые из них:
🔸 char.GetNumericValue
Метод возвращает численное значение символа:
🔸 char.GetUnicodeCategory
Метод возвращает UnicodeCategory, к которой относится символ:
🔸 char.IsControl
Метод возвращает признак того, является ли символ управляющим:
🔸 char.IsDigit
Метод возвращает признак того, является ли символ цифрой:
🔸 char.IsLetter
Метод возвращает признак того, является ли символ буквой:
🔸 char.IsLower
Метод возвращает признак того, что символ в нижнем регистре:
🔸 char.IsNumber
Метод возвращает признак того, является ли символ числом:
За скобками остались методы проверки символов на пунктуацию, приведения к определённому регистру и другие, найти которые вы сможете в документации к типу Char.
💬 Хотели бы больше информации и общения по C# и dotNET? На днях канал @devdigest опубликовал отличную, на мой взгляд, подборку тематических ресурсов, которой не премину поделиться и с вами.
#data_types
Тип
char
в .NET содержит большое количество статических методов, которые позволяют проверить и получить необходимую информацию о символе.Далее я перечислю лишь некоторые из них:
🔸 char.GetNumericValue
Метод возвращает численное значение символа:
char.GetNumericValue('7'); // 7
char.GetNumericValue('¼'); // 0.25
char.GetNumericValue('Ⅸ'); // 9
🔸 char.GetUnicodeCategory
Метод возвращает UnicodeCategory, к которой относится символ:
char.GetUnicodeCategory('a');
// LowercaseLetter
char.GetUnicodeCategory('2');
// DecimalDigitNumber
char.GetUnicodeCategory("Upper Case", 6);
// UppercaseLetter
🔸 char.IsControl
Метод возвращает признак того, является ли символ управляющим:
char.IsControl('a'); // false
char.IsControl('\t'); // true
🔸 char.IsDigit
Метод возвращает признак того, является ли символ цифрой:
char.IsDigit('a'); // false
char.IsDigit('¼'); // false
char.IsDigit('3'); // true
🔸 char.IsLetter
Метод возвращает признак того, является ли символ буквой:
char.IsLetter('%'); // false
char.IsLetter('P'); // true
🔸 char.IsLower
Метод возвращает признак того, что символ в нижнем регистре:
char.IsLower('j'); // true
char.IsLower('Y'); // false
🔸 char.IsNumber
Метод возвращает признак того, является ли символ числом:
char.IsNumber('a'); // false
char.IsNumber('¼'); // true
За скобками остались методы проверки символов на пунктуацию, приведения к определённому регистру и другие, найти которые вы сможете в документации к типу Char.
💬 Хотели бы больше информации и общения по C# и dotNET? На днях канал @devdigest опубликовал отличную, на мой взгляд, подборку тематических ресурсов, которой не премину поделиться и с вами.
#data_types
Литералы типа float и суффикс f в C#
Предполагаю, что тема числовых типов уже порядком вам поднадоела, однако, мне ещё есть чем с вами поделиться 🙂
Итак.. давайте проинициализируем несколько переменных типа
Есть ли на ваш взгляд здесь упущение?
С точки зрения разработчика - всё в порядке, тип
Прежде чем я объясню почему так происходит, давайте разберём первую строку и вспомним правила приведения типов:
В данном случае значение
Таким образом нам следует запомнить, что согласно спецификации языка C# тип любого целочисленного литерала определяется в зависимости от его значения (
Отлично, надеюсь с этим разобрались. Так а что же со второй строкой? На самом деле- всё просто, и вот вторая вещь, которую нам стоит запомнить:
Любой дробный литерал в исходном коде является значением типа
Сделать это компилятор нам позволить не может, ввиду того, что такое приведение типов не может быть осуществлено неявно, а значит, мы должны явно (explicitly) дать понять, что нас это устраивает, следующим образом:
В завершение мне осталось лишь отметить, что типы
💬 Если у вас после прочитанного ещё остались силы и интерес к данной теме, то я порекомендую вам замечательную статью Jon Skeet - Binary floating point and .NET и раздел Literals спецификации языка C# уже в качестве самостоятельного изучения 😉
#data_types
Предполагаю, что тема числовых типов уже порядком вам поднадоела, однако, мне ещё есть чем с вами поделиться 🙂
Итак.. давайте проинициализируем несколько переменных типа
float
целочисленным и дробным значениями:float f1 = 4;
float f2 = 4.2;
Есть ли на ваш взгляд здесь упущение?
С точки зрения разработчика - всё в порядке, тип
float
является типом с плавающей точкой и может хранить в себе оба указанных нами значения. Однако компилятор иного мнения, т.к. во второй строке мы получим ошибку компиляции:Literal of type double cannot be implicitly converted to type 'float'.
Прежде чем я объясню почему так происходит, давайте разберём первую строку и вспомним правила приведения типов:
float f1 = 4;
В данном случае значение
4
является литералом типа int
, который, в свою очередь, компилятор сумеет привести к типу float
неявно (implicitly) ввиду отсутствия вероятности потери точности. Если этот момент вам не совсем понятен, я предлагаю вам вернуться к следующей заметке и вспомнить правила приведения типов.Таким образом нам следует запомнить, что согласно спецификации языка C# тип любого целочисленного литерала определяется в зависимости от его значения (
int
, uint
, long
, ulong
). Отлично, надеюсь с этим разобрались. Так а что же со второй строкой? На самом деле- всё просто, и вот вторая вещь, которую нам стоит запомнить:
Любой дробный литерал в исходном коде является значением типа
double
, а значит мы пытаемся присвоить значение типа double
переменной float
.Сделать это компилятор нам позволить не может, ввиду того, что такое приведение типов не может быть осуществлено неявно, а значит, мы должны явно (explicitly) дать понять, что нас это устраивает, следующим образом:
float f2 = (float)4.2;
float f3 = 4.2f;
В завершение мне осталось лишь отметить, что типы
float
и double
являются двоичными типами с плавающей запятой, соответствующими стандарту IEEE 754, а значит значение 4.2
будет представлено как 4.19999980926513671875E0
для float
(32 бита) и 4.20000000000000017763568394003E0
для double
(64 бита).💬 Если у вас после прочитанного ещё остались силы и интерес к данной теме, то я порекомендую вам замечательную статью Jon Skeet - Binary floating point and .NET и раздел Literals спецификации языка C# уже в качестве самостоятельного изучения 😉
#data_types
Переполнение в C#
Во время выполнения арифметических операций с целочисленными типами возможна ситуация, при которой итоговый результат выходит за рамки доступных значений результирующего типа (другими словами не может быть корректно представлен и размещён в памяти).
Такая ситуация называется переполнением (overflow) и корнями уходит к арифметике и битовому представлению чисел в computer science.
По умолчанию в C# и .NET при переполнении ведущие биты обнуляются, после чего результат "умещается" в доступных битах. В случае с беззнаковыми типами - большие значения станут меньше, а при переполнении знаковых типов положительные числа станут отрицательными.
Позвольте мне продемонстрировать это поведение на примерах:
🔸 Максимальным значением для беззнакового типа
🔸
🔸 Минимальное значение для знакового типа
💬 Как я уже упоминал ранее, использовать минимальные и максимальные значения в циклах стоит с осторожностью, иначе это может привести к неожиданным последствиям. Примером тому служит следующий код. Что выведется на экране? Ответ вы сможете найти ниже на странице 😉
#data_types
Во время выполнения арифметических операций с целочисленными типами возможна ситуация, при которой итоговый результат выходит за рамки доступных значений результирующего типа (другими словами не может быть корректно представлен и размещён в памяти).
Такая ситуация называется переполнением (overflow) и корнями уходит к арифметике и битовому представлению чисел в computer science.
По умолчанию в C# и .NET при переполнении ведущие биты обнуляются, после чего результат "умещается" в доступных битах. В случае с беззнаковыми типами - большие значения станут меньше, а при переполнении знаковых типов положительные числа станут отрицательными.
Позвольте мне продемонстрировать это поведение на примерах:
🔸 Максимальным значением для беззнакового типа
uint
является 0xffffffff:uint u1 = 0xffffffff;
u1 = u1 + 5; // 0x00000004 (перенос)
🔸
int.MaxValue
- максимальное значение для знакового типа int
:int n1 = int.MaxValue;
n1 = n1 + 1; // -2147483648 (перенос)
🔸 Минимальное значение для знакового типа
short
является -32768:short s1 = short.MinValue;
s1 = (short)(s1 - 1); // 32767 (перенос)
💬 Как я уже упоминал ранее, использовать минимальные и максимальные значения в циклах стоит с осторожностью, иначе это может привести к неожиданным последствиям. Примером тому служит следующий код. Что выведется на экране? Ответ вы сможете найти ниже на странице 😉
#data_types
Контроль переполнения в C#
В прошлой заметке я уже упоминал о том, что по умолчанию при переполнении ведущие биты обнуляются, после чего результат "умещается" в доступных битах:
Однако это поведение может быть изменено и для этого мы можем воспользоваться ключевым словом
В таком случае вместо продолжения выполнения программы будет выброшено исключение
Синтаксис ключевого слова
Также стоит упомянуть о возможности изменения поведения при переполнении в настройках проекта:
#data_types
В прошлой заметке я уже упоминал о том, что по умолчанию при переполнении ведущие биты обнуляются, после чего результат "умещается" в доступных битах:
int n1 = int.MaxValue;
n1 = n1 + 1; // -2147483648 (перенос)
Однако это поведение может быть изменено и для этого мы можем воспользоваться ключевым словом
checked
:int n1 = int.MaxValue;
n1 = checked(n1 + 1); // OverflowException
В таком случае вместо продолжения выполнения программы будет выброшено исключение
OverflowException
, которое мы можем или корректно обработать, или проигнорировать для завершения программы.Синтаксис ключевого слова
checked
позволяет контролировать переполнение не только у конкретных операторов, но и целых блоков кода:checked
{
int n1 = int.MaxValue;
n1 = n1 + 1; // OverflowException
}
Также стоит упомянуть о возможности изменения поведения при переполнении в настройках проекта:
Project > Properties > Build > Advanced > Check for arithmetic overflow/underflow
.#data_types