C# Programming · Lessons 1–12
C# Programming Study Guide: Online Final Exam
Review console programs, types, strings, operators, flow control, collections, generics, exceptions, enumerations, classes, and methods for Lessons 1–12.
How to use this guide
This guide condenses Lessons 1–12 into the ideas, vocabulary, comparisons, and code patterns you should be able to explain and use. For each lesson, explain the idea in plain language, trace the short examples by hand and predict their output, and then write a small variation from memory without copying the code.
The ideas that connect Lessons 1–12
The course builds one workflow in three stages. Lessons 1–4 create a console program, store information in typed variables, treat text as structured data, and combine values with operators. Lessons 5–8 control which statements run, organize groups of values in collections, define how those values are visited, and use generics so the same code works safely with many types. Lessons 9–12 make programs dependable and organized: exceptions report and handle errors, enumerations name fixed sets of values, classes bundle related data and behavior into objects, and methods divide work into reusable, named steps.
When you study, connect each syntax choice to its purpose: the type describes what a value means, the variable gives it a name, the operator transforms or compares it, control flow decides what runs, the collection groups values, the class models a thing, and the method names an action.
Lesson 1 — Creating Your First C# Console Application
Foundation vocabulary
A programming language is a compromise between what computers understand and what humans understand. A program is a sequence of instructions; a project is the folder and configuration that contains all the files needed to build a program; and an executable file is the built program that an operating system can run. A Windows program is commonly an executable that can be started by double-clicking its icon; Windows executable files use the .exe extension.
A pattern is a description of how an object looks (not a description of its purpose); matching a pattern means recognizing that description in an object.
- C# is the programming language: you write statements using its grammar and type system.
- .NET supplies the runtime and class libraries that C# programs use. Modern .NET is cross-platform.
- Visual Studio is an IDE that combines editing, project management, building, debugging, and configuration.
- A project contains the source files and settings needed to build one application. A solution can organize one or more related projects.
- A C# source file normally ends in
.cs.
Build, run, and inspect
The normal development cycle is create a project, edit the source, build, run, and inspect the result. Building compiles the source and reports syntax or type errors. Running executes the built program. A successful build proves that the code is valid enough for the compiler; it does not prove that the program’s logic produces the intended result.
Traditional program structure
An explicit console entry point makes the major pieces visible:
using System;
namespace Program1
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, C#!");
}
}
}
using System; makes names from the System namespace available without repeating the full namespace. A namespace groups related names, a class contains code, Main is the traditional starting method, braces define a block, and semicolons end statements. A console application starts executing right after the statement containing Main() and ends at the closing curly brace (}) that follows Main().
Top-level statements and console input/output
C# can also use top-level statements for a small console program. The compiler creates the entry point behind the scenes:
Console.Write("Enter your name: ");
string name = Console.ReadLine() ?? "";
Console.WriteLine($"Hello, {name}!");
Console.Write keeps the cursor on the same line; Console.WriteLine adds a line break. Console.ReadLine returns the entered line as a string and may return null, so ?? "" supplies an empty string when needed. Console.Read reads a character value and is sometimes used to pause a console window. C# has no Console.log or Console.Display method.
Comments document intent and are ignored when the program runs:
// This is a single-line comment.
/* This is a
multi-line comment. */
Lesson 1 terms to know
C#, .NET, Visual Studio, IDE, program, programming language, project, solution, executable, .exe, .cs file, namespace, class, Main, Console.Write, Console.WriteLine, Console.ReadLine, Console.Read, top-level statements, comment, pattern
Lesson 2 — Living with Variability — Declaring Value-Type Variables
Variables, identifiers, and initialization
A variable is like a small box that stores a value for later use. Declaring a variable creates it with a specific type and name and optionally assigns a starting value (its initializer). Declare and assign a variable before reading it. Identifiers are case-sensitive, cannot begin with a digit, and cannot be reserved keywords.
int seats = 24;
double fee = 15.50;
bool isOpen = true;
DateTime today = DateTime.Today;
Intrinsic variable types, such as int, double, and bool (and close derivatives like unsigned int), are built right into the processor. The intrinsic types plus decimal are also called value types because the variable stores the actual data. string is neither an intrinsic type nor a value type: its variable stores a reference (a sort of pointer) to the string’s data.
Numeric types and arithmetic
The int type is a signed 32-bit (4-byte) integer: it stores whole numbers only, both negative and positive, in a range of roughly −2 billion through +2 billion. A signed integer can represent both negative and positive values. The decimal type stores base-10 values with 28–29 significant digits, representing magnitudes from about 10⁻²⁸ up to about 10²⁸.
Use integer types for whole numbers. int is the usual default; long provides a larger range, while short and byte are useful for narrower domains or formats. Integer division discards the fractional part, so at least one operand must be fractional when you want a fractional result.
int whole = 5 / 9; // 0
double fraction = 5.0 / 9.0; // approximately 0.555...
int remainder = 25 % 3; // 1
bool isEven = 24 % 2 == 0; // true
float and double use approximate binary floating-point storage; double is the common general-purpose choice. decimal is designed for base-10 financial calculations and uses the m suffix:
decimal price = 19.95m;
int quantity = 3;
decimal subtotal = price * quantity;
bool, char, string, and DateTime
bool stores true or false. char stores one character and uses single quotes, such as 'A'. string stores a sequence of characters and uses double quotes, such as "IST 2373".
char grade = 'A';
string message = "First line\nSecond line";
string path = @"C:\documents\files\myFile.txt";
DateTime now = DateTime.Now;
DateTime tomorrow = now.AddDays(1);
bool leapYear = DateTime.IsLeapYear(2028);
An ordinary string interprets escape sequences such as \n; a verbatim string prefixed with @ treats backslashes as literal characters. DateTime.Today represents the current date at midnight, while DateTime.Now includes the current local time.
Value types, constants, and var
Numeric types, bool, char, and DateTime are value types: assigning one variable to another copies the value. string is a reference type with immutable behavior; string operations create new string values rather than changing an existing string in place.
const declares a named value that cannot be reassigned and must be initialized immediately. var asks the compiler to infer a local variable’s type from its initializer; it remains strongly typed.
const int Capacity = 24;
var count = 5; // inferred as int
var title = "C#"; // inferred as string
Conversions and casts
Casting a fractional value to an integer performs integer truncation: it lops off the fractional part rather than rounding to the nearest whole number.
A widening conversion (promotion) moves to a type that can represent all source values and can usually happen implicitly, such as int to double. A narrowing conversion (demotion), such as double to int, may lose information and must be explicit. A cast is a type enclosed in parentheses placed immediately in front of the variable or expression.
int count = 10;
double precise = count; // implicit widening conversion (promotion)
double average = 92.8;
int whole = (int)average; // explicit narrowing cast (demotion); result is 92
Lesson 2 terms to know
variable, declaration, identifier, initialization, intrinsic type, int, signed integer, long, short, byte, float, double, decimal, bool, char, string, DateTime, DateTime.Today, DateTime.Now, const, var, value type, reference type, widening conversion, narrowing conversion, promotion, demotion, cast, integer truncation, modulo
Lesson 3 — Pulling Strings
Strings are immutable sequences
A string is an ordered sequence of characters. A string literal is a set of characters surrounded by double quotation marks; a character literal uses single quotation marks. Strings are immutable: an operation that appears to change a string returns a new string, leaving the original unchanged. Store the returned value when you need the changed text.
string original = " C# Programming ";
string trimmed = original.Trim();
string upper = trimmed.ToUpper(); // "C# PROGRAMMING"
string lower = trimmed.ToLower(); // "c# programming"
Two string literals joined with + become one string with no space added between them unless the text includes one:
string s = "this is a phrase" + " and so is this";
// this is a phrase and so is this
High-use string operations
| Need | Common member or pattern |
|---|---|
| Find the number of characters | Length |
| Access one position | indexer, such as text[0] |
| Extract part of a string | Substring |
| Search for text | Contains, IndexOf, LastIndexOf |
| Search for any of several characters | IndexOfAny, LastIndexOfAny |
| Test a beginning or ending | StartsWith, EndsWith |
| Change case or remove outer spaces | ToUpper, ToLower, Trim |
| Replace text | Replace |
| Separate or combine pieces | Split, Join |
Indexes start at zero. The last valid index is Length - 1. Check a string’s length before indexing or use a search method that reports whether the requested text exists.
Searching and comparing strings
IndexOf searches from the beginning; LastIndexOf searches from the end. IndexOfAny and LastIndexOfAny search for any character in a supplied character array. All of these return a zero-based index, or -1 when nothing is found.
string food = "cheeseburgers"; // indexes 0–12
int lastR = food.LastIndexOf('r'); // 11
int lastEorR = food.LastIndexOfAny(new[] { 'e', 'r' }); // 11
int firstABC = food.IndexOfAny(new[] { 'a', 'b', 'c' }); // 0 ('c' at index 0)
String.Compare("cat", "rabbit") returns -1 because cat sorts before rabbit; it returns 0 for equal strings and 1 when the first string sorts after the second.
Whitespace means characters that normally do not display on the screen, such as spaces, tabs, and line breaks. Trim removes whitespace at the ends of a string; it does not remove internal spaces.
Character validation and string construction
Char.IsDigit tests whether a character is a decimal digit. A method that trims input, checks Length, loops through characters, and returns false for any non-digit is validating content rather than converting it to a number. Passing "Hello" to such a method returns false.
Substring(start, length) begins at a zero-based index and copies the requested number of characters. Replace returns a new string with matching characters or substrings changed, and Split chops one string into an array of smaller strings using a delimiter:
string food = "cheeseburgers";
string sub = food.Substring(6, food.Length - 7); // "burger"
string text = "Danger NoSmoking".Replace(' ', '!'); // "Danger!NoSmoking"
string[] parts = "red,green,blue".Split(',');
Join performs the reverse operation by placing a separator between the items of a sequence. foreach (char c in text) visits each character in order; Console.Write(c) prints them back-to-back without inserting spaces or commas.
string[] brothers = { "Chuck", "Bob", "Steve", "Mike" };
Console.WriteLine(string.Join(":", brothers)); // Chuck:Bob:Steve:Mike
To capitalize only the first character, convert that character to a string and append the unchanged remainder:
string s1 = "chuck";
string s2 = char.ToUpper(s1[0]).ToString()
+ s1.Substring(1, s1.Length - 1); // "Chuck"
Use == for an ordinary value comparison of strings. Equals and StringComparison make the comparison intent more explicit when case or culture matters.
string answer = "yes";
bool matches = answer.Equals("YES", StringComparison.OrdinalIgnoreCase);
Read, parse, and format text
String.Format uses numbered placeholders such as {0}, {1}, and {2} that are filled by the arguments that follow the format string:
string formatted = String.Format("{0} times {1} equals {2}", 2, 5, 2 * 5);
// 2 times 5 equals 10
An interpolated string combines standard text and variables or expressions to build a finalized string. It begins with $ before the opening quote and places expressions inside braces:
string name = "George";
int age = 44;
string stat = $"{name} is {age} years old."; // George is 44 years old.
Without the $, the braces are printed literally. Format specifiers can control display, such as $"{subtotal:C}" for currency.
Console input arrives as text. A common pipeline is read a line, validate or parse it, then use the typed result:
Console.Write("Enter a whole number: ");
string input = Console.ReadLine() ?? "";
if (int.TryParse(input, out int number))
{
Console.WriteLine($"Double: {number * 2}");
}
else
{
Console.WriteLine("Please enter digits.");
}
TryParse avoids an exception for ordinary invalid input.
Repeated string construction
For a small number of values, concatenation or interpolation is clear. When a loop repeatedly builds a large string, StringBuilder is usually more efficient because it is designed for incremental changes.
Lesson 3 terms to know
string, string literal, immutability, Length, index, Substring, Contains, IndexOf, LastIndexOf, IndexOfAny, LastIndexOfAny, String.Compare, StartsWith, EndsWith, ToUpper, ToLower, Trim, whitespace, Replace, Split, Join, Char.IsDigit, Equals, StringComparison, String.Format, TryParse, interpolated string, StringBuilder
Lesson 4 — Smooth Operators
Arithmetic and precedence
Arithmetic operators include +, -, *, /, and % (remainder, or modulo). Parentheses make grouping explicit. Without parentheses, multiplication, division, and remainder are evaluated before addition and subtraction; operators at the same precedence are generally evaluated left to right.
int n = 5 + 3 * 2; // 11
int m = (5 + 3) * 2; // 16
int r = 5 % 3; // 2
Be aware of operand types: integer operands produce integer division, while a double or decimal operand produces a corresponding fractional calculation.
Assignment and increment operators
The assignment operator = stores a value; it does not test equality. Compound assignments combine an operation with assignment:
int score = 10;
score += 5; // score = score + 5
score *= 2; // score = score * 2
score++; // add 1
++ and -- change a variable by one. In an expression, prefix form changes the value before it is used and postfix form uses the old value before changing it:
int n = 1;
int p = ++n; // n becomes 2, then p is 2
n = 1;
int m = n++; // m gets 1, then n becomes 2
Relational and equality operators
<, >, <=, and >= compare ordering. == tests equality and != tests inequality. These operators produce a bool, which can be assigned to a variable or used in a conditional.
For calculated floating-point values, exact equality can be unreliable because float and double store approximations. Compare with an acceptable tolerance when appropriate. Use decimal when the domain requires base-10 financial precision.
Logical operators and short-circuiting
The logical operators combine Boolean expressions:
| Operator | Meaning |
|---|---|
! |
not |
&& |
and; both sides must be true |
| ` | |
^ |
exclusive or; exactly one side is true |
&& and || short-circuit: C# skips the right side when the left side already determines the result. The single-character operators & and | produce the same Boolean result but always evaluate both operands. A bool prints as True or False.
bool a = true;
bool b = false;
Console.WriteLine((a | b) + ", " + (a || b)); // True, True
Console.WriteLine(a && b); // False
if (index >= 0 && index < values.Length)
{
Console.WriteLine(values[index]);
}
Expressions, conversions, and overloads
An expression is a construction of operators and operands (values, variables, and method calls) that produces a result. Its type matters. C# applies implicit conversions (promotion) when safe, but a cast is needed for demotion because it could lose information.
Many operators are overloaded: the same symbol can perform an operation appropriate to its operands. For example, + adds numeric values but concatenates strings when one operand is text.
int total = 2 + 3; // numeric addition
string label = "Total: " + total; // string concatenation
Lesson 4 terms to know
operator, operand, expression, precedence, associativity, +, -, *, /, %, =, compound assignment, ++, --, prefix, postfix, relational operator, equality operator, !, &&, ||, ^, &, |, short-circuit evaluation, implicit conversion, explicit cast, promotion, demotion, operator overloading
Lesson 5 — Getting into the Program Flow
Expressions, statements, and method calls
An expression is a construction of operators and operands (for example, x > y) that combines literals, variables, operators, and method calls to produce a value. A statement performs an action, often by assigning a value, calling a method, or controlling execution. The purpose of a method call is to delegate some work to a method: you write the method name followed by parentheses, put arguments inside the parentheses, and use, pass along, or ignore the return value.
string line = Console.ReadLine() ?? "";
decimal amount = Convert.ToDecimal(line);
Console.WriteLine(amount * 1.07m);
ReadLine() and ToDecimal() are method calls. Declaring a variable such as int n = 1 is not a method call. Likewise, Animal dog = new Animal() creates an object rather than calling a method. A method’s return type tells you what value comes back; a void method performs an action without returning a value.
Control-flow patterns
The three basic types of flow control are the if statement (decisions), the loop (repetition), and the jump (break, continue, return). The list below shows the same ideas in more detail:
- Sequence runs statements in their written order.
- Selection chooses among paths with
if,else, orswitch. - Iteration repeats a block with
for,while,do, orforeach. - Jumps transfer control:
breakexits the nearest loop or switch,continueskips to the next loop iteration, andreturnexits the current method.
Hard-coding means placing a value directly in source code rather than creating a variable, reading input, or calculating it.
Selection
An if statement lets a program take different paths depending on the result of a bool expression. Decision-making makes programs far more useful than programs that always do the same thing. The body runs only when the condition is true; the optional else body runs when it is false.
if (score >= 70)
{
Console.WriteLine("Passing");
}
else
{
Console.WriteLine("Keep practicing");
}
Use switch when one expression is compared with several constant values. Its restrictions:
- The switch expression must be a counting (integral) type, including
charandenum, or astring. - Each
casevalue must be the same type as the switch expression. - Each
casevalue must be a constant. - Each nonempty case must end with
break,return, or another jump. (There is nopassstatement in C#.)
Loops
A for loop runs its initializer once, tests the condition, runs the body when the condition is true, executes the increment expression at the closing brace, and tests the condition again. If the condition is false the first time, the body executes zero times:
for (int count = 0; count < 3; count++)
{
Console.WriteLine(count);
}
A while loop also tests first. A do...while loop tests at the end, so its body always runs at least once.
Enumerations
An enumeration (enum) is a list of value names to be used in place of bare numbers:
enum TrafficLight { Red, Yellow, Green }
TrafficLight light = TrafficLight.Green;
Lesson 10 covers enumerations in depth.
Lesson 5 terms to know
expression, statement, method call, argument, return value, flow control, sequence, selection, iteration, jump, if, else, switch, case, default, break, continue, return, for, while, do...while, enum, hard-coding
Lesson 6 — Lining Up Your Ducks with Collections
Arrays
An array holds a list of objects of the same type, indexed from zero. Its elements sit in contiguous, adjacent memory locations. Arrays expose Length. Putting a value of the wrong type in an array, such as a string in an int[], is caught by the compiler at build time.
int[] scores = new int[3];
scores[0] = 90;
int[] numbers = { 1, 2, 3 };
var primes = new[] { 2, 3, 5, 7, 11, 13 }; // newer syntax: element type inferred
Array limitations:
- The program must declare the size of an array when it is created; it cannot grow later.
- Inserting or removing an element in the middle of an array is inefficient because the other elements must shift.
Collections and lists
A collection is a kind of storage box that holds multiple values. Arrays and collections share useful traits: each holds elements of only one type (fixed at compile time), both have methods and properties, and collections can be accessed with array-like square-bracket index syntax. A dictionary is a generic collection that stores key/value pairs. Unlike an array, a collection such as List<T> resizes itself as items are added. List<T> exposes Count.
List<string> names = new List<string>(); // new, empty list
names.Add("Ada");
names.AddRange(new[] { "Grace", "Alan" });
string[] nameArray = names.ToArray(); // list to array
The foreach loop works with arrays and every other collection type, and it also handles multidimensional arrays. It does not require you to manage an index or a length.
Hash sets
HashSet<T> is an unordered collection with no duplicate items, like a mathematical set. Its set methods modify the set on which they are called:
| Method | Result kept in the first set |
|---|---|
IntersectWith(other) |
values present in both sets |
ExceptWith(other) |
values in the first set but not in other |
UnionWith(other) |
every unique value from either set |
HashSet<string> colors = new() { "red", "orange", "yellow" };
colors.UnionWith(new[] { "orange", "yellow", "green", "blue", "violet" });
// red orange yellow green blue violet
HashSet<int> odds = new() { 1, 3, 5, 7, 9, 11, 13, 15 };
odds.ExceptWith(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 17 });
// 11 13 15
HashSet<T> has no AddRange method.
Queues and priority queues
A queue is first-in, first-out (FIFO). Enqueue adds to the back; Dequeue removes and returns the oldest item from the front. foreach visits the remaining items from front to back. Both the generic Queue<T> and the older non-generic System.Collections.Queue behave this way.
Queue<string> zoo = new();
zoo.Enqueue("Fox");
zoo.Enqueue("Cat");
zoo.Enqueue("Lion");
zoo.Enqueue("Elephant");
zoo.Dequeue(); // removes "Fox"
// remaining order: Cat Lion Elephant
A priority queue serves the most important items first. In the lesson’s package example, a high-priority package goes to the front of the line, but behind any other high-priority packages already there. PriorityQueue<TElement,TPriority> removes the element with the smallest priority value first by default:
PriorityQueue<string, int> jobs = new();
jobs.Enqueue("normal", 5);
jobs.Enqueue("urgent", 1);
string nextJob = jobs.Dequeue(); // "urgent"
Lesson 6 terms to know
array, index, Length, collection, List<T>, Count, Add, AddRange, Insert, Remove, ToArray, foreach, HashSet<T>, IntersectWith, ExceptWith, UnionWith, Queue<T>, FIFO, Enqueue, Dequeue, priority queue, PriorityQueue<TElement,TPriority>, set, dictionary
Lesson 7 — Stepping through Collections
Enumerators and foreach
An IEnumerable<T> represents a sequence that can provide an enumerator. An IEnumerator<T> is the cursor that walks through that sequence, so code can iterate a collection without knowing how the collection stores its items. An enumerator can also iterate over part of a collection and performs late (deferred) execution. MoveNext() advances the cursor, Current returns the item at the current position, Reset() sets the cursor back to the beginning when supported, and Dispose() releases resources.
IEnumerable<int> values = new[] { 2, 4, 6 };
using IEnumerator<int> cursor = values.GetEnumerator();
while (cursor.MoveNext())
{
Console.WriteLine(cursor.Current);
}
foreach is convenient syntax for obtaining an enumerator, repeatedly calling MoveNext, reading Current, and disposing the enumerator.
Iterator methods
An iterator method produces a sequence one item at a time. yield return combines the work of MoveNext() and Current: it hands the next item to the caller and preserves the method’s state. yield break ends the sequence immediately.
static IEnumerable<int> EvenNumbers(int limit)
{
for (int value = 0; value <= limit; value++)
{
if (value % 2 == 0)
yield return value;
}
yield break;
}
Advantages of iterators:
- Each collection class can define its own way to iterate.
- Application code can create several independent iterators over the same collection.
- Application code does not need to know how the collection works internally.
- Items are produced lazily (late execution): the next item is calculated only when the consumer asks for it. Reduced memory use and faster performance are not among the defining advantages of the iterator approach.
Lesson 7 terms to know
IEnumerable<T>, IEnumerator<T>, GetEnumerator, MoveNext, Current, Reset, Dispose, foreach, iterator, yield return, yield break, lazy evaluation
Lesson 8 — Buying Generic
Generic types and methods
Generics let a type or method work with a type parameter instead of one hard-coded element type. They provide reuse while preserving compile-time type safety.
static T First<T>(T[] items)
{
return items[0];
}
string firstName = First(new[] { "Ada", "Grace" });
int firstScore = First(new[] { 95, 88 });
List<T>, Queue<T>, HashSet<T>, and IEnumerable<T> are generic forms. The type argument, such as string or int, determines what the object can contain.
Invariance, covariance, and contravariance
| Term | Plain-language meaning | Keyword |
|---|---|---|
| Covariance | An instance of a subclass (more-derived type) can be used where an instance of a parent class is expected. | out |
| Contravariance | An instance of a superclass (less-derived type) can be used where an instance of a subclass is expected. | in |
| Invariance | Neither covariance nor contravariance is possible; the type argument must match exactly. | none |
Even though string derives from object, List<string> is not a List<object>: mutable types such as List<T> are invariant. This protects type safety because a broader reference could otherwise insert an unrelated value.
IEnumerable<string> words = new List<string>();
IEnumerable<object> objects = words; // covariance
Action<object> acceptAnything = value => Console.WriteLine(value);
Action<string> acceptString = acceptAnything; // contravariance
Wrappers and factories
A wrapper is a class (or method) that encapsulates complexity. It contains other objects, manages what goes into them and how, and presents an interface that can be quite different from the interfaces of what is inside it. In the lesson’s priority-queue example, the wrapper decides which underlying queue each package goes into.
public sealed class Box<T>
{
public Box(T value) => Value = value;
public T Value { get; }
}
A factory is a class or method that creates objects for its users. It separates object creation from the code that uses the object:
static List<T> CreateList<T>() => new List<T>();
List<int> numbers = CreateList<int>();
Lesson 8 terms to know
generic, type parameter, type argument, type safety, invariance, covariance, out, contravariance, in, wrapper, factory, List<T>, IEnumerable<T>, Action<T>
Lesson 9 — Some Exceptional Exceptions
The exception mechanism
An exception is an object that reports an error. When code throws one, normal execution stops and C# searches back through the calling methods for a matching catch block. The keywords are try, catch, finally, and throw.
try
{
int value = int.Parse(input);
Console.WriteLine(100 / value);
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Cannot divide by zero: {ex.Message}");
}
catch (FormatException)
{
Console.WriteLine("Please enter a whole number.");
}
finally
{
Console.WriteLine("Done.");
}
- A
tryblock surrounds code that might fail. - A
catchblock handles a particular exception type. List the most specific types first and general types such asExceptionlast. - A
finallyblock runs whether or not an exception occurred and is used for cleanup such as closing files. throwraises an exception yourself:throw new ArgumentException("Age cannot be negative.");
The finally block does not run when the process ends abnormally: a StackOverflowException, OutOfMemoryException, or ExecutionEngineException (also written ExecutingEngineException) occurs; the program terminates because a fatal exception is not handled; or an outside application kills the process.
Why exceptions are better than error codes
The exception mechanism is expressive (it can model many error-handling strategies), consistent (every error is reported the same way, which promotes understanding), and informative (an exception carries its type, message, and stack trace, which aids debugging). It also leads to more readable code, but it is not a way to make a program run faster.
Custom exceptions
A custom exception is a class that derives from Exception so that callers can catch an error that is specific to your program:
public class InsufficientFundsException : Exception
{
public InsufficientFundsException(string message) : base(message) { }
}
Planning an exception-handling strategy
Questions to ask while writing a program:
- What could go wrong?
- If something goes wrong, can the program recover?
- How robust (unbreakable) does the code need to be?
- What about exceptions that slip through the cracks and elude the handlers?
- Does the problem put user data at risk?
- Where should the exception handler go, and which exceptions should it handle?
- What sort of input errors might the application see?
Guidelines for code that handles errors well:
- Catch the most specific exception you can handle.
- Don’t let the program continue running if the problem cannot be recovered.
- Protect the user’s data at all costs, and don’t crash.
- Throw an exception when a method cannot complete its task, but don’t use exceptions as part of the normal flow of execution (for example, to exit a loop).
- Don’t catch an exception in a method until it can be handled in a useful way, preferably by recovering from the error.
- Test thoroughly, especially with bad input.
- Put a last-chance handler in
Main()(or wherever the top of the program is), except in reusable class libraries. - Consider custom exception classes when they add debugging information or clearer messages.
- Check the documentation’s Exceptions section to find out which methods throw which exceptions.
Last-chance handlers and throw expressions
A last-chance handler catches exceptions that nothing else handled, usually to log them before the program ends:
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
Console.WriteLine($"Unhandled: {e.ExceptionObject}");
A throw expression lets throw appear inside an expression, such as with the null-coalescing or conditional operator:
string name = input ?? throw new ArgumentNullException(nameof(input));
Lesson 9 terms to know
exception, try, catch, finally, throw, Exception, Message, stack trace, DivideByZeroException, FormatException, ArgumentException, custom exception, robust code, last-chance handler, UnhandledException, throw expression
Lesson 10 — Creating Lists of Items with Enumerations
When to use an enumeration
An enumeration gives readable names to a fixed set of related values, such as days, colors, or order statuses. Rules of thumb:
- The collection must present a stable, unchanging list of members.
- Each member must remain stable and present a recognizable, consistent value.
- If the numeric value associated with a particular word keeps changing, an enumeration will not work well.
Declaring enumerations
By default, the first member is 0 and each following member is one more than the previous one. Casting a member to int shows its number, and printing the member shows its name:
enum Colors { Red, Orange, Yellow, Green, Blue, Purple }
Console.WriteLine($"Color Name: {Colors.Blue}"); // Color Name: Blue
Console.WriteLine($"Color Value: {(int)Colors.Blue}"); // Color Value: 4
An initializer sets the starting value of an enumeration member. Initializers can use constants, other members, arithmetic, hexadecimal, and bitwise operators:
enum Colors2
{
Red = 5,
Orange = 10,
Yellow = Orange + 5, // 15
Green = 5 * 4, // 20
Blue = 0x19, // 25
Purple = Orange | Green // 30
}
You can also specify the underlying data type, which must be an integral type: enum Sizes : byte { Small, Medium, Large }.
Enumerated flags
Flags track small configuration options that are normally on or off. Mark the enumeration with [Flags], give each member a power of two, and combine members with |:
[Flags]
enum Permissions { None = 0, Read = 1, Write = 2, Execute = 4 }
Permissions p = Permissions.Read | Permissions.Write;
bool canWrite = p.HasFlag(Permissions.Write); // true
Console.WriteLine(p); // Read, Write
Enumerated switches and loops
Enumerations work naturally with switch because each case is a named constant:
switch (light)
{
case TrafficLight.Red:
Console.WriteLine("Stop");
break;
case TrafficLight.Green:
Console.WriteLine("Go");
break;
default:
Console.WriteLine("Caution");
break;
}
Because members have numeric values, a for loop can step through a range of them:
for (Colors item = Colors.Orange; item <= Colors.Blue; item++)
{
Console.WriteLine("{0} = {1}", item, (int)item);
}
// Orange = 1, Yellow = 2, Green = 3, Blue = 4 (one per line)
Enumeration methods
| Method | Purpose |
|---|---|
Enum.GetValues(typeof(Colors)) |
every member, in value order |
Enum.GetNames(typeof(Colors)) |
every member name as a string |
Enum.Parse / Enum.TryParse |
convert a name string to a member |
Enum.IsDefined |
test whether a value or name exists |
ToString() |
the member’s name |
foreach (Colors item in Enum.GetValues(typeof(Colors)))
{
Console.WriteLine($"{item} = {(int)item}"); // Red = 0 ... Purple = 5
}
Lesson 10 terms to know
enum, enumeration member, underlying type, initializer, hexadecimal literal, [Flags], bitwise |, HasFlag, enumerated switch, Enum.GetValues, Enum.GetNames, Enum.Parse, Enum.TryParse, Enum.IsDefined, typeof
Lesson 11 — Showing Some Class
Object-oriented programming basics
A class is a bundling of unlike data and functions that logically belong together into one tidy package. An object is one instance of a class created with new, much like a real-world entity such as a chair or a car. A method is a way to split code into smaller pieces; it is not the same thing as a class. The four core OOP principles:
| Principle | Meaning |
|---|---|
| Abstraction | Focus on what is needed in the real world rather than the underlying programming details. |
| Encapsulation | Keep an object’s data and code together in one unit, so everything needed is in one place, and keep the data safe by controlling access to it through the object’s own members. |
| Inheritance | Extend an existing class to meet other needs; a subclass gets the base class’s members. |
| Polymorphism | A member defined in a base class can take a more specific form in a subclass, such as a Roof property in Vehicle that Car further defines. |
Defining a class and creating objects
public class Vehicle
{
public string manufacturer;
public int numberOfWheels;
}
Vehicle myCar = new Vehicle();
myCar.manufacturer = "Rambler"; // access members with the dot operator
Vehicle yourCar = new(); // C# 9.0 target-typed new
Each object has its own copy of the class’s instance data, so two Vehicle objects can hold different manufacturers. That is how code discriminates between objects.
References
A class variable holds a reference to an object, not the object itself. Assigning one reference to another creates a second reference that points to the same object; it does not copy the object:
Vehicle yourCar = new Vehicle();
yourCar.manufacturer = "Rambler";
Vehicle yourSpousalCar = yourCar; // same object, two references
yourSpousalCar.manufacturer = "Ford";
Console.WriteLine(yourCar.manufacturer); // Ford
A reference that points to no object is null. A class can contain members whose types are other classes, such as a Car that contains a Motor object.
Static, const, and readonly members
A static member belongs to the class itself rather than to any one object, so access it through the class name:
public class Car
{
public static int numberOfCars; // shared by all cars
public string licensePlate; // one per car
}
Car.numberOfCars++; // correct: class name
Car newCar = new Car();
newCar.licensePlate = "ABC 123"; // instance member: object name
A const data member is fixed at compile time and is automatically static. A readonly data member can be set in its declaration or in a constructor, and then cannot change.
Lesson 11 terms to know
class, object, instance, new, member, dot operator, abstraction, encapsulation, inheritance, polymorphism, reference, null, target-typed new, static, const, readonly
Lesson 12 — We Have Our Methods
Defining and using a method
A method is a named block of code that performs one task. Its declaration lists an access level, optional static, the return type, the name, and a parameter list in parentheses:
static double Average(int[] values)
{
int sum = 0;
foreach (int v in values) sum += v;
return (double)sum / values.Length;
}
double avg = Average(new[] { 90, 85, 96 });
Refactoring means cleaning up existing code so that it is easier to read or follows the organization’s usual requirements, without changing what it does, such as moving repeated code into a method. A local function is a method declared inside another method and usable only there.
Arguments and parameters
A parameter is part of the method’s definition: the complete listing of the inputs it expects. An argument is the actual value given as input when the method is called. Arguments are passed by listing them in the parentheses that follow the method name, and they must match the parameters in number, type, and order.
static void Greet(string name, int times) { /* ... */ }
Greet("Ada", 3); // "Ada" and 3 are arguments
Overloading means defining several methods with the same name but different parameter lists; C# chooses the one that matches the arguments. A default argument supplies a value when the caller omits it:
static double Area(double side) => side * side;
static double Area(double width, double height) => width * height;
static void Log(string message, bool timestamp = true) { /* ... */ }
Log("Started"); // timestamp is true
Call by reference and void methods
A method can return a value to its caller with a return statement or through call-by-reference parameters. ref passes an existing variable that the method can change; out lets the method assign a value the caller receives:
static void Double(ref int n) => n *= 2;
int x = 5;
Double(ref x); // x is now 10
static bool TryHalf(int n, out int half)
{
half = n / 2;
return n % 2 == 0;
}
A void method returns no value. It ends when it reaches a return; with no value or when control reaches the method’s closing brace.
Returning multiple values with tuples
A tuple groups several values so a method can return more than one result. The classic Tuple is a kind of dynamic array nominally containing two items that can be read as a key/value pair, though that is not strictly required. Modern syntax names the elements:
static (int min, int max) Range(int[] values) => (values.Min(), values.Max());
var result = Range(new[] { 4, 9, 2 });
Console.WriteLine($"{result.min} to {result.max}"); // 2 to 9
var pair = Tuple.Create("Ada", 95); // Item1 = "Ada", Item2 = 95
The Tuple class provides versions for one through seven items; an eighth type parameter, TRest, nests another tuple to hold longer lists.
Lesson 12 terms to know
method, return type, return, void, parameter, argument, refactoring, local function, method overloading, default argument, call by reference, ref, out, tuple, Tuple.Create, Item1, Item2
Course Notes