Skip to main content
Published / updated

Variables, Types, and Operators

Before you start

You need: a console project you can run (Article 01). Nothing else.

Time: about 50 minutes, plus the practice.

Learning objective

Choose the right type for each piece of School Management System data, convert user input safely, and use operators without producing wrong numbers.

Topics

  • Declaring variables
  • Value types and reference types
  • Choosing a numeric type — and why money is decimal
  • Strings and interpolation
  • DateTime
  • Conversion and parsing
  • Nullable types and null
  • Operators and precedence

Declaring variables

string studentName = "Ravi Kumar";
string rollNumber = "NCA-2024-0012";
int studentId = 12;
decimal totalFees = 15000.00m;
bool isActive = true;
DateTime joiningDate = new DateTime(2024, 6, 15);

Declare one variable per line, with its type written out.

C# also allows var, where the compiler infers the type. It is common in production code and this track does not use it. When you are learning, var studentId = GetId(); hides the one fact you most need to see. Write the type; read the type.

Naming, as the whole .NET ecosystem does it:

KindConventionExample
Local variablecamelCasetotalFees
Method, property, classPascalCaseCalculateTotalFees, RollNumber, Student
Private field_camelCase_feeRepository
ConstantPascalCaseMaximumMarks

Name what it holds, not what type it is. studentCount beats intVar; n tells the next reader nothing.

Value types and reference types

The distinction that explains a whole category of surprising bugs.

HoldsAssignment copiesExamples
Value typeThe data itselfThe valueint, decimal, bool, DateTime, enum, struct
Reference typeThe address of the dataThe referencestring, arrays, List<T>, every class
int marksA = 85;
int marksB = marksA;
marksB = 90;
Console.WriteLine(marksA); // 85 — unaffected

Student first = new Student();
first.Name = "Ravi Kumar";

Student second = first;
second.Name = "Priya Sharma";

Console.WriteLine(first.Name); // Priya Sharma

first and second are two names for one object. Changing it through either name changes it. Nothing was copied.

This is why passing a Student into a method and modifying it changes the caller's object, while passing an int does not.

string is a reference type but behaves like a value type in one way: strings are immutable. Every operation that appears to change a string actually produces a new one.

string name = "Ravi";
name = name + " Kumar"; // a new string; the original is discarded

Numeric types

TypeRangeUse for
int±2.1 billionIds, counts, marks, ages
longVery largeLarge ids, milliseconds
decimal28–29 significant digits, base-10Money — always
double~15–16 digits, base-2Scientific measurement, averages
float~7 digits, base-2Rarely; low-precision graphics

Money is decimal, never double or float.

double doubleTotal = 0.1 + 0.2;
Console.WriteLine(doubleTotal); // 0.30000000000000004

decimal decimalTotal = 0.1m + 0.2m;
Console.WriteLine(decimalTotal); // 0.3

double stores base-2 fractions, and 0.1 has no exact base-2 representation — the same reason 1/3 has no exact decimal one. Sum a thousand FeePayment amounts as double and the total is wrong by paise. decimal stores base-10 and sums exactly.

A fee report that is off by a few paise is this, every time. So Salary, TotalFees, PaidAmount, DiscountAmount and Amount are all decimal.

The m suffix marks a decimal literal. Without it, 15000.00 is a double and will not compile into a decimal.

decimal totalFees = 15000.00m;
decimal discount = 2500.50m;
decimal payable = totalFees - discount; // 12499.50

Strings

string name = "Ravi Kumar";

Console.WriteLine(name.Length); // 10
Console.WriteLine(name.ToUpper()); // RAVI KUMAR
Console.WriteLine(name.Contains("Kumar")); // True
Console.WriteLine(name.Substring(0, 4)); // Ravi
Console.WriteLine(name.Replace("Ravi", "Arjun")); // Arjun Kumar
Console.WriteLine(" NCA-2024-0012 ".Trim()); // NCA-2024-0012

Trim matters more than it looks. A roll number read from a file or a form often carries trailing whitespace, and "NCA-2024-0012 " == "NCA-2024-0012" is false. The comparison fails, the student is "not found", and nothing is visibly wrong.

string studentName = "Ravi Kumar";
string rollNumber = "NCA-2024-0012";
decimal balance = 3000.50m;

string summary = $"{studentName} ({rollNumber}) owes {balance:N2}";
// Ravi Kumar (NCA-2024-0012) owes 3,000.50

Interpolation with $ is clearer than + concatenation and supports formatting: :N2 for two decimal places, :dd-MMM-yyyy for dates.

Checking for empty input:

string input = Console.ReadLine();

if (string.IsNullOrWhiteSpace(input))
{
Console.WriteLine("Roll number is required.");
return;
}

IsNullOrWhiteSpace covers null, empty and spaces-only in one call. input == "" misses two of the three.

DateTime

DateTime joiningDate = new DateTime(2024, 6, 15);
DateTime today = DateTime.Today;
DateTime now = DateTime.UtcNow;

Console.WriteLine(joiningDate.ToString("dd-MMM-yyyy")); // 15-Jun-2024

TimeSpan served = today - joiningDate;
Console.WriteLine(served.Days);

DateTime dueDate = joiningDate.AddMonths(3);
MemberReturns
DateTime.NowLocal date and time of the machine
DateTime.UtcNowCoordinated Universal Time
DateTime.TodayLocal date, time set to midnight

Use DateTime.UtcNow for anything stored or compared. A server set to UTC and a developer in IST are five and a half hours apart — an attendance record saved at 11 pm IST lands on the previous day in UTC. Convert to local only when displaying.

DateTime subtraction gives a TimeSpan, not a number. Read .Days or .TotalDays from it.

Conversion and parsing

Console input is always string. Turning it into a number is where beginners meet their first crash.

string input = Console.ReadLine();

int marks = int.Parse(input); // throws FormatException on bad input
string input = Console.ReadLine();
int marks;

if (int.TryParse(input, out marks))
{
Console.WriteLine($"Marks recorded: {marks}");
}
else
{
Console.WriteLine("Enter a whole number between 0 and 100.");
}

TryParse is the correct choice for anything a user typed. It returns true or false instead of throwing, and puts the result in the out variable. Parse is for input you already know is valid.

decimal amount;

if (decimal.TryParse(input, out amount))
{
// safe to use
}

Implicit and explicit conversion between numeric types:

int marks = 85;
decimal marksAsDecimal = marks; // implicit — no data can be lost

decimal exactFees = 15000.75m;
int roundedFees = (int)exactFees; // explicit cast — 15000, the .75 is discarded

A cast truncates; it does not round. (int)15000.75m is 15000, not 15001. Use Math.Round when rounding is what you mean.

decimal average = 78.6m;
int rounded = (int)Math.Round(average); // 79

Nullable types and null

null means "no value". A reference type can be null; a value type cannot, unless you mark it.

int? marksObtained = null; // nullable int
decimal? discountAmount = null;
DateTime? lastLoginAt = null;

This is exactly how an absent student is stored.

public class ExamResult
{
public int Id { get; set; }
public int StudentId { get; set; }
public int? MarksObtained { get; set; } // null when absent — never 0
public bool IsAbsent { get; set; }
}

An absent student's marks are null, never 0. Store 0 and the class average is silently wrong, because a zero is included in the calculation and an absence should not be. Nothing errors; the report is just incorrect.

Reading a nullable value:

if (result.MarksObtained.HasValue)
{
int marks = result.MarksObtained.Value;
Console.WriteLine($"Marks: {marks}");
}
else
{
Console.WriteLine("Absent");
}
int marks = result.MarksObtained ?? 0; // null-coalescing: use 0 if null
int? length = student.Name?.Length; // null-conditional: null if Name is null

?. stops the whole expression if the left side is null instead of throwing. ?? supplies a fallback.

Use ?? deliberately. result.MarksObtained ?? 0 is the very substitution that corrupts an average. It is correct for display, wrong for arithmetic.

Operators

int total = 85 + 72;
int difference = 85 - 72;
int product = 5 * 12;
int quotient = 85 / 4; // 21 — integer division discards the remainder
int remainder = 85 % 4; // 1

85 / 4 is 21, not 21.25. Two integers divide to an integer. To get the fraction, one side must be non-integer:

decimal average = 85m / 4m; // 21.25
decimal average2 = (decimal)85 / 4; // 21.25

This is a real bug source. A class average computed as totalMarks / studentCount with both int silently drops the decimal part.

GroupOperators
Arithmetic+ - * / %
Comparison== != < > <= >=
Logical&& || !
Assignment= += -= *= /=
Null?? ??= ?.
bool isPassing = marks >= subject.PassingMarks;
bool isEligible = student.IsActive && student.Status == StudentStatus.Active;

&& and || short-circuit. In a && b, if a is false, b is never evaluated. That is what makes this safe:

if (account != null && account.PaidAmount > 0)
{
Console.WriteLine("Payment received.");
}

Reverse the order and it throws — the null check must come first.

Precedence: * and / before + and -; comparison before &&; && before ||. Use brackets rather than remembering the table. (a && b) || c costs nothing and removes all doubt.

Errors you will hit

MessageCauseFix
CS0029: Cannot implicitly convert type 'string' to 'int'Assigned text to a numberint.TryParse it
CS0266: Cannot implicitly convert type 'double' to 'decimal'Mixed the two money-ish typesUse decimal throughout, with the m suffix
CS0664: Literal of type double cannot be implicitly converted to 'decimal'Wrote 15000.00 where a decimal was expectedWrite 15000.00m
System.FormatException: The input string 'abc' was not in a correct formatint.Parse on something that is not a numberint.TryParse and handle false
System.OverflowExceptionValue too large for the typeUse long, or check the input
Answer is 21 when you expected 21.25Integer divisionMake one side decimal

The m suffix catches everyone once. decimal total = 15000.00; does not compile — the literal is a double until you write 15000.00m.

Common mistakes

  • double or float for money
  • Forgetting the m suffix on a decimal literal
  • int.Parse on user input instead of TryParse
  • Assuming a cast rounds
  • Integer division when a fraction was intended
  • Storing an absent student's marks as 0
  • ?? 0 inside an average or total
  • DateTime.Now for stored values instead of UtcNow
  • Comparing strings without trimming
  • input == "" instead of IsNullOrWhiteSpace
  • Assigning one object to another and expecting a copy

Practice

  1. Declare one variable of each type for a Student: Name, RollNumber, ClassName, DateOfBirth, and one for FeeAccount.TotalFees.
  2. Sum 0.1 + 0.2 as double and as decimal. Print both.
  3. Sum a thousand payments of 1234.56 as double and as decimal, and compare the totals.
  4. Assign one Student object to a second variable, change the second's Name, and print the first's.
  5. Do the same with two int variables and note the difference.
  6. Read a roll number, compare it to "NCA-2024-0012" with and without a trailing space, then fix it with Trim.
  7. Read marks with int.Parse and type abc. Read the exception.
  8. Rewrite it with TryParse and handle the failure.
  9. Compute a class average of 85 + 72 + 91 over 3 students using int division, then correctly.
  10. Cast 15000.75m to int and confirm it truncates. Then use Math.Round.
  11. Model an absent ExamResult with MarksObtained = null and IsAbsent = true. Compute the class average once treating null as 0, once excluding it, and compare.
  12. Use ?. on a Student whose Name is null, then ?? to supply a fallback.
  13. Write a null check using && in the wrong order and observe the exception, then fix it.
  14. Format a fee balance with :N2 and a joining date with :dd-MMM-yyyy.

Exercises 3 and 11 are the two whose consequences reach real money and real report cards.

You can now

  • Choose the right type for each piece of School data
  • Say why money is decimal and never double
  • Convert user input safely with TryParse
  • Explain what happens when you assign one object to another
  • Say why an absent student's marks are null, not 0
  • Spot integer division before it gives you a wrong average

Review questions

  1. Why must money be decimal rather than double?
  2. What happens when you assign one Student variable to another and change one?
  3. When should you use TryParse rather than Parse?
  4. Why is storing an absent student's marks as 0 wrong, given that nothing errors?

Next: Control flow and methods