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:
| Kind | Convention | Example |
|---|---|---|
| Local variable | camelCase | totalFees |
| Method, property, class | PascalCase | CalculateTotalFees, RollNumber, Student |
| Private field | _camelCase | _feeRepository |
| Constant | PascalCase | MaximumMarks |
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.
| Holds | Assignment copies | Examples | |
|---|---|---|---|
| Value type | The data itself | The value | int, decimal, bool, DateTime, enum, struct |
| Reference type | The address of the data | The reference | string, 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
| Type | Range | Use for |
|---|---|---|
int | ±2.1 billion | Ids, counts, marks, ages |
long | Very large | Large ids, milliseconds |
decimal | 28–29 significant digits, base-10 | Money — always |
double | ~15–16 digits, base-2 | Scientific measurement, averages |
float | ~7 digits, base-2 | Rarely; 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);
| Member | Returns |
|---|---|
DateTime.Now | Local date and time of the machine |
DateTime.UtcNow | Coordinated Universal Time |
DateTime.Today | Local 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.
| Group | Operators |
|---|---|
| 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
| Message | Cause | Fix |
|---|---|---|
CS0029: Cannot implicitly convert type 'string' to 'int' | Assigned text to a number | int.TryParse it |
CS0266: Cannot implicitly convert type 'double' to 'decimal' | Mixed the two money-ish types | Use decimal throughout, with the m suffix |
CS0664: Literal of type double cannot be implicitly converted to 'decimal' | Wrote 15000.00 where a decimal was expected | Write 15000.00m |
System.FormatException: The input string 'abc' was not in a correct format | int.Parse on something that is not a number | int.TryParse and handle false |
System.OverflowException | Value too large for the type | Use long, or check the input |
Answer is 21 when you expected 21.25 | Integer division | Make 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
doubleorfloatfor money- Forgetting the
msuffix on a decimal literal int.Parseon user input instead ofTryParse- Assuming a cast rounds
- Integer division when a fraction was intended
- Storing an absent student's marks as
0 ?? 0inside an average or totalDateTime.Nowfor stored values instead ofUtcNow- Comparing strings without trimming
input == ""instead ofIsNullOrWhiteSpace- Assigning one object to another and expecting a copy
Practice
- Declare one variable of each type for a
Student:Name,RollNumber,ClassName,DateOfBirth, and one forFeeAccount.TotalFees. - Sum
0.1 + 0.2asdoubleand asdecimal. Print both. - Sum a thousand payments of
1234.56asdoubleand asdecimal, and compare the totals. - Assign one
Studentobject to a second variable, change the second'sName, and print the first's. - Do the same with two
intvariables and note the difference. - Read a roll number, compare it to
"NCA-2024-0012"with and without a trailing space, then fix it withTrim. - Read marks with
int.Parseand typeabc. Read the exception. - Rewrite it with
TryParseand handle the failure. - Compute a class average of
85 + 72 + 91over3students usingintdivision, then correctly. - Cast
15000.75mtointand confirm it truncates. Then useMath.Round. - Model an absent
ExamResultwithMarksObtained = nullandIsAbsent = true. Compute the class average once treating null as0, once excluding it, and compare. - Use
?.on aStudentwhoseNameis null, then??to supply a fallback. - Write a null check using
&&in the wrong order and observe the exception, then fix it. - Format a fee balance with
:N2and 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
decimaland neverdouble - 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, not0 - Spot integer division before it gives you a wrong average
Review questions
- Why must money be
decimalrather thandouble? - What happens when you assign one
Studentvariable to another and change one? - When should you use
TryParserather thanParse? - Why is storing an absent student's marks as
0wrong, given that nothing errors?
Next: Control flow and methods