cSharp Articles
C# Files C# Enums C# Interfaces C# Abstraction C# polymorphism C# inheritance guide C# access modifiers c# constructors C# class members C# class objects C# method overloading C# return values c# methods C# array sorting C# arrays C# forEach loop C# strings C# user input c# data type C# variables whats C#Data types are one of those things you deal with constantly in C#, whether you're consciously thinking about them or not. Every single variable you create is tied to a specific type, and that type dictates exactly what kind of data it's allowed to hold.
It's a bit like sorting containers in a kitchen — you wouldn't pour soup into a box meant for dry cereal, and in the same way, you wouldn't try to stuff a block of text into a variable meant for whole numbers. Let's go through the fundamentals and get a clear picture of how this all works.
So What Is a Data Type, Really?
Put simply, a data type defines what kind of value a variable is allowed to store. C# splits these into two broad categories — value types and reference types — and understanding the difference between them tells you a lot about how your data actually behaves in memory.
Value Types vs. Reference Types
Value types hold their data directly. When you assign one value-type variable to another, C# makes a full, independent copy of that value — the two variables have nothing to do with each other after that point. int, float, and bool are all classic examples.
Reference types, on the other hand, don't hold the actual data themselves — they hold a reference pointing to where that data lives in memory. So when you assign one reference-type variable to another, you're copying the reference, not the data itself, which means both variables end up pointing at the exact same underlying object. string, arrays, and custom objects all fall into this category.
Here's a side-by-side example that makes the difference pretty obvious:
int a = 10; // Value type
int b = a; // b is now 10, independent of a
a = 20; // changing 'a' does not change 'b'
Console.WriteLine(b); // Outputs: 10
string str1 = "Hello"; // Reference type
string str2 = str1; // str2 references the same string object as str1
str1 = "Goodbye"; // changing 'str1' does not change 'str2'
Console.WriteLine(str2); // Outputs: Hello
Interesting note here — even though string is technically a reference type, it behaves a bit like a value type in this example because strings in C# are immutable. Reassigning str1 doesn't modify the original string object; it points str1 at a brand-new one entirely, leaving str2 untouched.
The Value Types You'll Use Constantly
Whole Numbers
int— the default choice for whole numbers, covering roughly -2.1 billion to 2.1 billion.short— a smaller-range integer, useful when memory is tight, going from -32,768 to 32,767.long— for when you need numbers bigger thanintcan handle, stretching into the quintillions.
Decimal Numbers
float— single-precision, 32-bit floating point.double— double-precision, 64-bit floating point, and generally the more common default when you need decimals.
True/False Values
bool— holds nothing buttrueorfalse, which makes it the natural fit for conditions and branching logic.
Putting a few of these together:
int age = 30;
float temperature = 98.6f;
bool isRaining = false;
Console.WriteLine($"Age: {age}, Temperature: {temperature}, Is it raining? {isRaining}");
The Reference Types You'll Run Into Most Often
Strings
string is what you'll reach for anytime you're dealing with text. One quirk worth knowing: strings are immutable in C#, meaning once a string object is created, it can't actually be changed in place — any "modification" is really creating a new string behind the scenes.
Arrays
Arrays let you hold a bunch of values of the same type together in one structure. They're especially handy when you're working with a known, fixed-size list of data — a set of scores, a batch of names, that kind of thing.
Objects
C# also lets you define your own classes, and any instance of a class is a reference type. Objects can bundle together all sorts of data and behavior, which makes them the backbone of most real-world C# applications.
Here's all three in action:
string greeting = "Hello, World!";
string[] colors = { "Red", "Green", "Blue" };
var person = new { Name = "John", Age = 30 }; // Anonymous object
Console.WriteLine(greeting);
foreach (var color in colors)
{
Console.WriteLine(color);
}
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
Handling "No Value" with Nullable Types
Sometimes a variable genuinely doesn't have a value yet — think of an empty box rather than one holding a zero or an empty string. That's exactly the scenario nullable types are built for. A nullable type can hold every value its underlying type normally would, plus one extra possibility: null. You mark a type as nullable just by tacking a question mark onto the end of it.
int? optionalValue = null;
if (optionalValue.HasValue)
{
Console.WriteLine(optionalValue.Value);
}
else
{
Console.WriteLine("Value is null");
}
This comes up a lot more than you'd expect — anywhere a value might legitimately be "unknown" or "not yet set," rather than just zero.
Converting Between Types
At some point you'll need to move data from one type to another, and C# handles this in two different ways.
Implicit conversion happens automatically, and only when C# is confident it can make the conversion without losing any data — like converting an int to a double, since every whole number fits cleanly into a decimal type.
Explicit conversion, also called casting, is what you use when you're going the other direction — squeezing a larger or more precise type into a smaller one. This one comes with a real risk: you can lose data in the process, since the compiler can't guarantee everything will fit.
double pi = 3.14;
int wholePi = (int)pi; // Explicit conversion
Console.WriteLine(wholePi); // Outputs: 3
Notice how casting 3.14 down to an int just chops off the decimal portion entirely — that .14 is gone for good, which is exactly the kind of data loss explicit conversions can introduce if you're not careful.