Перейти к основному содержимому

Выберите правильный тип данных в коде C#.

Введение

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

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

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

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

Цели обучения

В этом модуле вы узнаете:

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

Откройте для себя типы значений и ссылочные типы

Поскольку в C# доступно множество типов данных, выбор правильного из них означает, что вам нужно понять, когда вы можете предпочесть один тип данных другому.

Прежде чем обсуждать, почему вы можете предпочесть один тип данных другому, вам нужно больше узнать о типах данных. Вам также нужно знать, как данные и типы данных работают в C# и .NET.

Что такое данные?

Ответ на вопрос «что такое данные» зависит от того, кого вы спрашиваете, и в каком контексте вы его задаете.

При разработке программного обеспечения данные - это, по сути, значение, которое хранится в памяти компьютера в виде серии битов. Бит - это простой двоичный переключатель, представленный в виде 0 или 1, или, скорее, «выключено» и «включено». Одиночный бит не кажется полезным, однако если объединить 8 битов в последовательность, они образуют байт. При использовании байта каждый бит приобретает определенное значение в последовательности. На самом деле, используя двоичную систему счисления (основание-2), вы можете представить 256 различных комбинаций с помощью всего 8 бит.

Например, в двоичной системе счисления число 195 можно представить как 11000011. Следующая таблица поможет вам представить, как это работает. В первой строке восемь столбцов, которые соответствуют позиции в байте. Каждая позиция представляет собой отдельное числовое значение. Вторая строка может хранить значение отдельного бита - 0 или 1.

1286432168421
11000011

Если сложить числа из каждого столбца первой строки, которым соответствует 1 во второй строке, то получится десятичный эквивалент представления двоичной системы счисления. В данном случае это будет 128 + 64 + 2 + 1 = 195.

Чтобы работать с большими значениями, чем 255, ваш компьютер хранит больше байт (обычно 32- или 64-битных). Если вы работаете с миллионами больших чисел в научной среде, вам, возможно, придется более тщательно выбирать типы данных, которые вы используете. Для выполнения вашего кода может потребоваться больше памяти.

А как насчет текстовых данных?

Если компьютер понимает только 0 и 1, то как он позволяет работать с текстом? Используя такую систему, как ASCII (American Standard Code for Information Interchange), вы можете использовать один байт для представления заглавных и строчных букв, цифр, табуляции, пробела, новой строки и многих математических символов.

Например, если бы вы хотели сохранить строчную букву a в качестве значения в моем приложении, компьютер понял бы только двоичную форму этого значения. Чтобы лучше понять, как строчная буква a обрабатывается компьютером, мне нужно найти таблицу ASCII, в которой представлены значения ASCII и их десятичные эквиваленты. Вы можете найти такой ресурс в Интернете по запросу «ASCII lookup decimal».

В данном случае строчная буква a эквивалентна десятичному значению 97. Затем вы можете использовать ту же двоичную систему счисления в обратном порядке, чтобы найти, как буква ASCII a хранится в компьютере.

1286432168421
01100001

Поскольку 64 + 32 + 1 = 97, 8-битный двоичный код ASCII для a будет 01100001.

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

Что такое тип данных?

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

Значения и ссылочные типы

В этом модуле рассматриваются два вида типов в C#: ссылочные типы и типы значений.

Переменные ссылочных типов хранят ссылки на свои данные (объекты), то есть они указывают на значения данных, хранящиеся где-то еще. В отличие от этого, переменные типов значений непосредственно содержат свои данные. По мере изучения C# появляются новые подробности, связанные с фундаментальным различием между типами значений и ссылок.

Простые типы значений

Простые типы значений - это набор предопределенных типов, предоставляемых C# в виде ключевых слов. Эти ключевые слова являются псевдонимами (псевдонимами) для предопределенных типов, определенных в библиотеке классов .NET. Например, ключевое слово int в C# является псевдонимом типа значения, определенного в библиотеке классов .NET как System.Int32.

К простым типам значений относятся многие типы данных, которые вы, возможно, уже использовали, например char и bool. Также существует множество интегральных типов и типов значений с плавающей точкой для представления широкого спектра целых и дробных чисел.

Выводы

  • Значения хранятся в виде битов, которые представляют собой простые переключатели "вкл/выкл". Комбинируя достаточное количество таких переключателей, можно хранить практически любые возможные значения. - Существует две основные категории типов данных: типы значений и ссылочные типы. Разница заключается в том, как и где значения хранятся компьютером во время выполнения вашей программы.
  • Простые типы значений используют ключевое слово псевдоним для представления формальных имен типов в библиотеке .NET.

Откройте для себя интегральные типы

В этом упражнении вы работаете с интегральными типами. Интегральный тип - это простой тип значений, который представляет целые числа без дробей (например, -1, 0, 1, 2, 3). Самым популярным в этой категории является тип данных int.

Существует две подкатегории интегральных типов: знаковые и беззнаковые интегральные типы.

Знаковый тип использует свои байты для представления равного количества положительных и отрицательных чисел. В следующем упражнении вы познакомитесь со знаковыми интегральными типами в C#.

Используйте свойства MinValue и MaxValue для каждого типа знаковых интегралов.

  1. Убедитесь, что у вас открыт Visual Studio и на панели редактора отображается файл Program.cs. Program.cs должен быть пустым. Если это не так, выделите и удалите все строки кода.
  2. Чтобы увидеть диапазоны значений для различных типов данных, введите следующий код в редакторе кода Visual Studio.
Console.WriteLine("Signed integral types:");

Console.WriteLine($"sbyte : {sbyte.MinValue} to {sbyte.MaxValue}");
Console.WriteLine($"short : {short.MinValue} to {short.MaxValue}");
Console.WriteLine($"int : {int.MinValue} to {int.MaxValue}");
Console.WriteLine($"long : {long.MinValue} to {long.MaxValue}");
  1. Запустите приложение. вы должны увидеть следующий результат
Signed integral types:
sbyte : -128 to 127
short : -32768 to 32767
int : -2147483648 to 2147483647
long : -9223372036854775808 to 9223372036854775807

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

Беззнаковые интегральные типы

Тип unsigned использует свои байты для представления только положительных чисел. В оставшейся части упражнения мы познакомимся с беззнаковыми интегральными типами в C#.

Используйте свойства MinValue и MaxValue для каждого беззнакового интегрального типа

  1. Ниже предыдущего фрагмента кода добавьте следующий код:
Console.WriteLine("");
Console.WriteLine("Unsigned integral types:");

Console.WriteLine($"byte : {byte.MinValue} to {byte.MaxValue}");
Console.WriteLine($"ushort : {ushort.MinValue} to {ushort.MaxValue}");
Console.WriteLine($"uint : {uint.MinValue} to {uint.MaxValue}");
Console.WriteLine($"ulong : {ulong.MinValue} to {ulong.MaxValue}");
  1. Сохраните и запустите код. вы должны увидеть
Signed integral types:
sbyte : -128 to 127
short : -32768 to 32767
int : -2147483648 to 2147483647
long : -9223372036854775808 to 9223372036854775807

Unsigned integral types:
byte : 0 to 255
ushort : 0 to 65535
uint : 0 to 4294967295
ulong : 0 to 18446744073709551615

Хотя данный тип данных может использоваться во многих случаях, учитывая тот факт, что тип данных "байт" может представлять значение от 0 до 255, очевидно, что он предназначен для хранения значения, которое представляет собой байт данных. Данные, хранящиеся в файлах, или данные, передаваемые через Интернет, часто имеют двоичный формат. При работе с данными из таких внешних источников вам необходимо получать данные в виде массива байтов, а затем преобразовывать их в строки. Многие методы библиотеки классов .NET, связанные с кодированием и декодированием данных, требуют работы с массивами байтов.

Выводы

  • Интегральный тип - это простой тип данных, который может хранить целые числа.
  • Существуют знаковые и беззнаковые числовые типы данных. В знаковых интегральных типах используется 1 бит для хранения положительного или отрицательного значения.
  • Вы можете использовать свойства MaxValue и MinValue числовых типов данных, чтобы оценить, может ли число поместиться в заданный тип данных.

Знакомство с типами с плавающей точкой

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

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

Оценка типов с плавающей точкой

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

Во-вторых, необходимо учитывать способ хранения значений и его влияние на точность. Например, значения float и double хранятся в двоичном формате (основание 2), а десятичные значения - в десятичном формате (основание 10). Почему это важно?

Выполнение математических операций с двоичными значениями с плавающей точкой может привести к результатам, которые могут вас удивить, если вы привыкли к десятичной математике (основание 10). Часто двоичная математика с плавающей точкой является приближением к реальному значению. Поэтому float и double полезны тем, что большие числа можно хранить в небольшом объеме памяти. Однако float и double следует использовать только в тех случаях, когда приближение полезно. Например, при расчете брызг от снежка в видеоигре достаточно приблизиться на несколько тысячных.

Если вам нужен более точный ответ, используйте decimal. Каждое значение типа decimal занимает относительно много памяти, однако при выполнении математических операций оно дает более точный результат. Поэтому при работе с финансовыми данными или в любом другом сценарии, где вам нужен точный результат вычислений, следует использовать десятичную систему счисления.

Используйте свойства MinValue и MaxValue для каждого типа подписанных плавающих значений.

  1. Удалите или используйте оператор комментария строки //, чтобы закомментировать весь код из предыдущих упражнений.
  2. Чтобы увидеть диапазоны значений для различных типов данных, обновите свой код в редакторе кода Visual Studio следующим образом:
Console.WriteLine("");
Console.WriteLine("Floating point types:");
Console.WriteLine($"float : {float.MinValue} to {float.MaxValue} (with ~6-9 digits of precision)");
Console.WriteLine($"double : {double.MinValue} to {double.MaxValue} (with ~15-17 digits of precision)");
Console.WriteLine($"decimal: {decimal.MinValue} to {decimal.MaxValue} (with 28-29 digits of precision)");
  1. Вы должны увидеть следующий результат:
Floating point types:
float : -3.402823E+38 to 3.402823E+38 (with ~6-9 digits of precision)
double : -1.79769313486232E+308 to 1.79769313486232E+308 (with ~15-17 digits of precision)
decimal: -79228162514264337593543950335 to 79228162514264337593543950335 (with 28-29 digits of precision)

Как видите, float и double используют отличную от decimal систему счисления для представления своих наибольших и наименьших возможных значений. Но что означают эти обозначения?

Расшифровка больших значений с плавающей точкой

Поскольку типы с плавающей точкой могут с высокой точностью хранить большие числа, их значения можно представить с помощью "E-нотации", которая представляет собой форму научной нотации, означающую "умноженное на 10, возведенное в степень". Так, значение 5E+2 будет равно 500, потому что это эквивалентно 5 * 10^2, или 5 x 102.

Выводы

  • Тип с плавающей точкой - это простой тип данных, который может хранить дробные числа.
  • При выборе подходящего типа с плавающей точкой для вашего приложения необходимо учитывать не только максимальные и минимальные значения, которые он может хранить. Необходимо также учитывать, сколько значений может быть сохранено после десятичной дроби, как хранятся числа и как их внутреннее хранение влияет на результат математических операций.
  • Иногда значения с плавающей точкой могут быть представлены с помощью "E-нотации", когда числа становятся особенно большими.
  • Существует фундаментальная разница в том, как компилятор и среда выполнения обрабатывают десятичную дробь по сравнению с float или double, особенно при определении точности, необходимой для математических операций.

Откройте для себя ссылочные типы

К ссылочным типам относятся массивы, классы и строки. Ссылочные типы отличаются от типов значений способом хранения значений во время выполнения приложения. В этом упражнении вы узнаете, чем ссылочные типы отличаются от типов значений, и как использовать оператор new, чтобы связать переменную со значением в памяти компьютера.

Чем ссылочные типы отличаются от типов значений

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

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

Определите переменную ссылочного типа

  1. Удалите или используйте оператор комментария строки //, чтобы закомментировать весь код из предыдущих упражнений.
  2. Обновите код в редакторе кода Visual Studio следующим образом:
int[] data;

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

  1. Создайте экземпляр массива int с помощью ключевого слова new Обновите код в редакторе кода Visual Studio, чтобы создать и присвоить новый экземпляр массива int, используя следующий код:
int[] data;
data = new int[3];

Ключевое слово new сообщает .NET Runtime о необходимости создать экземпляр массива int, а затем согласовать с операционной системой сохранение в памяти массива размером в три значения int. .NET Runtime подчиняется и возвращает адрес памяти нового массива int. Наконец, адрес памяти сохраняется в переменной data. Элементы массива int по умолчанию имеют значение 0, поскольку это значение по умолчанию для int.

  1. Измените пример кода, чтобы выполнить обе операции в одной строке кода Две строки кода в предыдущем шаге обычно сокращаются до одной строки кода, чтобы и объявить переменную, и создать новый экземпляр массива int. Измените код из шага 3 следующим образом.
int[] data = new int[3];

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

Чем отличается строковый тип данных в C#?

Тип данных string также является ссылочным типом. Вам может быть интересно, почему при объявлении строки не используется оператор new. Это чистое удобство, предоставленное разработчиками C#. Поскольку тип данных string используется так часто, вы можете использовать этот формат:

string shortenedString = "Hello World!";
Console.WriteLine(shortenedString);

Однако за кулисами создается новый экземпляр System.String и инициализируется на "Hello World!".

Практические вопросы использования типов значений и ссылок

  1. Тип значения (int): В этом примере val_A и val_B - целые типы значений.
int val_A = 2;
int val_B = val_A;
val_B = 5;

Console.WriteLine("--Value Types--");
Console.WriteLine($"val_A: {val_A}");
Console.WriteLine($"val_B: {val_B}");

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

--Value Types--
val_A: 2
val_B: 5

Когда выполняется val_B = val_A, значение val_A копируется и сохраняется в val_B. Таким образом, при изменении val_B значение val_A остается неизменным.

  1. Тип ссылки (массив): В этом примере ref_A и ref_B являются типами ссылок на массив.
int[] ref_A= new int[1];
ref_A[0] = 2;
int[] ref_B = ref_A;
ref_B[0] = 5;

Console.WriteLine("--Reference Types--");
Console.WriteLine($"ref_A[0]: {ref_A[0]}");
Console.WriteLine($"ref_B[0]: {ref_B[0]}");

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

--Reference Types--
ref_A[0]: 5
ref_B[0]: 5

Когда выполняется ref_B = ref_A, ref_B указывает на ту же ячейку памяти, что и ref_A. Таким образом, при изменении ref_B[0] изменяется и ref_A[0], поскольку они оба указывают на одну и ту же ячейку памяти. Это ключевое различие между типами значений и ссылочными типами.

Выводы

  • Типы значений могут хранить меньшие значения и хранятся в стеке. Ссылочные типы могут хранить большие значения, а новый экземпляр ссылочного типа создается с помощью оператора new. Переменные ссылочного типа содержат ссылку (адрес памяти) на фактическое значение, хранящееся в куче.
  • К ссылочным типам относятся массивы, строки и классы.

Выберите правильный тип данных

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

Предположим, что вам предстоит создать новое приложение, которое будет получать, обрабатывать и хранить различные типы данных. Какие типы данных вы будете использовать?

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

Но как быть при работе с числовыми данными? Здесь существует 11 различных вариантов. Как выбрать правильный тип данных?

Выбор типа

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

Оценивая варианты, вы должны взвесить несколько важных соображений. Часто не существует единственно верного ответа, но некоторые ответы более правильны, чем другие.

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

Выбор типа данных может помочь установить границы для размера данных, которые вы можете хранить в этой конкретной переменной. Например, если вы знаете, что определенная переменная должна хранить только число от 1 до 10 000, иначе она выходит за границы ожидаемого, вы, скорее всего, избежите byte и sbyte, поскольку их диапазоны слишком малы.

Кроме того, вам, скорее всего, не понадобятся int, long, uint и ulong, поскольку они могут хранить больше данных, чем необходимо. Аналогично, вы, вероятно, пропустите float, double и decimal, если вам не нужны дробные значения. Вы можете сузить выбор до short и ushort, причем оба варианта могут быть жизнеспособными. Если вы уверены, что отрицательное значение не будет иметь смысла в вашем приложении, вы можете выбрать ushort (положительное беззнаковое целое число, от 0 до 65 535). Теперь любое значение, присвоенное переменной типа ushort за пределами границ от 0 до 65535, будет вызывать исключение, тем самым помогая вам обеспечить определенную степень проверки разумности в вашем приложении.

Начните с выбора типа данных, чтобы соответствовать данным (а не для оптимизации производительности).

У вас может возникнуть соблазн выбрать тип данных, который использует наименьшее количество битов для хранения данных, думая, что это повысит производительность вашего приложения. Однако один из лучших советов, касающихся производительности приложения (то есть скорости его работы), заключается в том, чтобы не проводить «преждевременную оптимизацию». Не поддавайтесь искушению угадать, какие части вашего кода, включая выбор типов данных, могут повлиять на производительность вашего приложения.

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

Выбирайте типы данных, основываясь на типах входных и выходных данных используемых библиотечных функций

Предположим, вам нужно работать с диапазоном лет между двумя датами. Поскольку приложение является бизнес-приложением, вы можете решить, что вам нужен только диапазон примерно от 1960 до 2200. Можно было бы попробовать работать с byte, поскольку он может представлять числа от 0 до 255.

Однако, когда вы посмотрите на встроенные методы классов System.TimeSpan и System.DateTime, вы поймете, что они в основном принимают значения типа double и int. Если вы выберете sbyte, то будете постоянно перебрасывать значения туда-сюда между sbyte и double или int. В этом случае, возможно, имеет смысл выбрать int, если вам не нужна точность до секунды, и double, если вам нужна точность до секунды.

Выбирайте типы данных с учетом их влияния на другие системы

Иногда необходимо учитывать, как информация будет потребляться другими приложениями или другими системами, например базой данных. Например, система типов SQL Server отличается от системы типов C#. В результате, прежде чем сохранять данные в базе данных, необходимо провести некоторое сопоставление между ними.

Если целью вашего приложения является взаимодействие с базой данных, то вам, скорее всего, придется подумать о том, как хранятся данные и какой объем данных хранится. Выбор более крупного типа данных может повлиять на объем (и стоимость) физической памяти, необходимой для хранения всех данных, которые будет генерировать ваше приложение.

Если сомневаетесь, придерживайтесь основ

Несмотря на то, что вы рассмотрели несколько вариантов, для начала работы вам следует отдать предпочтение подмножеству основных типов данных, включая:

  • int для большинства целых чисел
  • decimal для чисел, представляющих деньги
  • bool для истинных или ложных значений
  • string для буквенно-цифровых значений

Выбирайте специальные сложные типы для особых ситуаций

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

  • byte: работа с кодированными данными, поступающими из других компьютерных систем или использующими различные наборы символов.
  • double: работа с геометрическими или научными данными. double часто используется при создании игр, связанных с движением.
  • System.DateTime для работы с конкретными значениями даты и времени.
  • System.TimeSpan - диапазон лет / месяцев / дней / часов / минут / секунд / миллисекунд.

Выводы

При выборе типов данных для вашего кода есть свои соображения, а зачастую и несколько вариантов. Обдумайте свой выбор, и, если у вас нет веских причин, старайтесь придерживаться основных типов, таких как int, decimal, string и bool.

Заключение

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