Lesson 2: Data Types and Data Structures
In this chapter, we will explore the fundamental data types in C# and how they compare to those in JavaScript and TypeScript. Understanding these differences is crucial for a smooth transition from JS/TS to C#.
Overview of C# Data Types
C# is a statically typed language, meaning that variable types are known at compile time. This is in contrast to JavaScript, which is dynamically typed. Here’s a quick overview of the primary data types in C#:
- Value Types: These include simple types (e.g.,
int
,float
,char
,bool
) and structs. They hold their data directly. - Reference Types: These include classes, arrays, and strings. They store references to their data (objects) in memory.
- Nullable Types: C# provides the ability to assign
null
to value types usingNullable<T>
or the shorthandT?
.
Comparison with JavaScript/TypeScript
Primitive Types
In JavaScript, primitive types include number
, string
, boolean
, null
, undefined
, and symbol
. TypeScript adds type annotations but does not change the underlying JavaScript types. Here’s how they map to C#:
JavaScript/TypeScript | C# Type | Notes |
---|---|---|
number |
int , float , double |
C# has distinct numeric types. Use int for integers, float for single-precision, and double for double-precision floating-point numbers. |
string |
string |
C# has a string type, similar to JavaScript. It is immutable and supports various methods for manipulation. |
boolean |
bool |
C# uses bool for boolean values, similar to JavaScript’s true and false . |
null |
null |
C# allows null for reference types and nullable value types. However, null cannot be assigned to non-nullable value types directly. |
undefined |
N/A | C# does not have an equivalent to undefined . Uninitialized variables will throw an error. |
symbol |
N/A | C# does not have a direct equivalent to JavaScript’s symbol . |
bigint |
long , BigInteger |
C# has long for 64-bit integers and BigInteger for arbitrarily large integers (from System.Numerics ). |
object |
object |
C# has a base object type, similar to JavaScript’s Object . All types in C# derive from object . |
Type Inference
In TypeScript, type inference allows the compiler to automatically determine the type of a variable based on its value. C# also supports type inference with the var
keyword, but it is limited to local variables and cannot be used for fields or properties.
let number = 42;
// TypeScript infers 'number' from the value
let userName = "Hello";
// TypeScript infers 'string'
var number = 42;
// C# infers 'int'
var userName = "Hello";
// C# infers 'string'
Explicit Type Declaration
In TypeScript, you can explicitly declare types using annotations. C# requires explicit type declarations for variables, fields, and properties.
let age: number = 30;
// Explicit type declaration
let isActive: boolean = true;
// Explicit type declaration
int age = 30;
// Explicit type declaration
string userName = "John";
// Explicit type declaration
Type Safety
C# is a strongly typed language, meaning that type mismatches are caught at compile time. This is different from JavaScript, where type coercion can lead to unexpected behavior at runtime. For example, in C#, trying to assign a string to an integer variable will result in a compile-time error.
let num = 42;
num = "Hello";
// No error in javascript as the runtime will coerce the type from number to string. However, this can lead to bugs and is the whole point of TypeScript's type system.
let num: number = 42;
num = "Hello";
// TypeScript error: Type 'string' is not assignable to type 'number'
int num = 42;
num = "Hello";
// C# error: Cannot implicitly convert type 'string' to 'int'
Compartive Examples
String
In JavaScript/TypeScript, strings are immutable and can be manipulated using various methods. C# strings are also immutable and provide a rich set of methods for manipulation. While variables holding these primitive types can be reassigned to new values, the underlying values themselves cannot be altered.
let greeting = "Hello, World!";
greeting = greeting.toUpperCase();
// "HELLO, WORLD!" greeting is reassigned to a new string value while the actual string value remains unchanged the greeting variable now points to a new string object. The original "Hello, World!" string still exists in memory (until garbage collection if no longer referenced).
let greeting: string = "Hello, World!";
greeting = greeting.toUpperCase();
// "HELLO, WORLD!"
string greeting = "Hello, World!";
greeting = greeting.ToUpper();
// "HELLO, WORLD!"
Boolean
In JavaScript/TypeScript, booleans are straightforward. C# also uses bool
for boolean values, but it is more strict about type usage.
let isActive = true;
isActive = false;
// Valid
let isActive: boolean = true;
isActive = false;
// Valid
bool isActive = true;
isActive = false;
// Valid
Number
In JavaScript/TypeScript, numbers can represent both integers and floating-point values. C# distinguishes between different numeric types, such as int
, float
, and double
.
let count = 42;
// Number type
let price = 19.99;
// Number type
let hex = 0xFF;
// Hexadecimal number
let count: number = 42;
// Number type
let price: number = 19.99;
// Number type
let hex: number = 0xFF;
// Hexadecimal number
int count = 42;
// Integer type
double price = 19.99;
// Double type
int hex = 0xFF;
// Hexadecimal integer
Here we can see that C# requires explicit type declarations for different numeric types, which helps prevent errors related to type coercion.
Numeric Types
In JavaScript, all numbers are represented as double-precision floating-point values (64-bit). There is no distinction between integers and floating-point numbers. In TypeScript, you can specify types, but they still map to JavaScript’s number type. The only exception is the bigint
type, which allows for arbitrarily large integers.
In C#, however, numeric types are more granular, allowing developers to choose the appropriate type based on the size and range of the values they need to store. This can lead to more efficient memory usage and better performance in applications.
Compared to JavaScript/TypeScript, C# has a more extensive set of numeric types, each with specific sizes and ranges. This allows for better memory management and performance optimization.
Integral Numeric Types
C# has several integral numeric types, each with a specific size and range:
C# Type | Size (bits) | Range |
---|---|---|
sbyte |
8 | -128 to 127 |
byte |
8 | 0 to 255 |
short |
16 | -32,768 to 32,767 |
ushort |
16 | 0 to 65,535 |
int |
32 | -2,147,483,648 to 2,147,483,647 |
uint |
32 | 0 to 4,294,967,295 |
long |
64 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
ulong |
64 | 0 to 18,446,744,073,709,551,615 |
In this example byte size is used to store a small number, to save memory. Attempting to store a larger number in a smaller type will result in a compile-time error.
sbyte smallNumber = 100;
// Valid
smallNumber = 200;
// Compile-time error: Cannot implicitly convert type 'int' to 'sbyte'
Floating-Point Types
C# has two primary floating-point types: float
and double
. The float
type is a single-precision 32-bit floating-point number, while double
is a double-precision 64-bit floating-point number. The decimal
type is also available for high-precision decimal numbers, often used in financial calculations.
float pi = 3.14f;
// Single-precision
double e = 2.718281828459045;
// Double-precision
decimal price = 19.99m;
// High-precision decimal
Character Type
C# has a char
type for representing single 16-bit Unicode characters. This is similar to JavaScript’s string
type, but in C#, a char
is a single character, while a string
is a sequence of characters.
char initial = 'A';
// Single character
string greeting = "Hello, World!";
// Sequence of characteristics
Guid Type
C# has a Guid
type for representing globally unique identifiers. This is similar to JavaScript’s string
type, but in C#, a Guid
is a 128-bit integer that is used to uniquely identify objects.
Guid userId = Guid.NewGuid();
// Generate a new unique identifier