๐ŸŽฏ

C# Fundamentals

A Complete 8-Chapter Programming Course

Topics covered:
The CLR & dotnet CLI · Value types, struct, and real property syntax
switch expressions & null-conditional operators · virtual/override dispatch
Interfaces & explicit interface implementation · Unchecked-only exceptions
Collections & a first taste of LINQ

Exercises: 24 hands-on exercises with worked solutions
Format: A4 · Dark-theme code examples · framed against Java, Kotlin, and TypeScript
Course 1 of 2 · Intermediate/Advanced follows

Table of Contents

  1. Getting Started
  2. Variables & Basic Types
  3. Operators & Control Flow
  4. Classes & Objects
  5. Inheritance & Polymorphism
  6. Interfaces & Abstract Classes
  7. Exception Handling
  8. Collections & a First Taste of LINQ
Chapter 1 of 8

Getting Started

Course 1 ยท Ch 1
Getting Started
Same two-stage compilation model as java1-1's JVM โ€” C# was built as Microsoft's direct answer to Java

This course sits deliberately next to Java throughout โ€” not because the two are interchangeable, but because they share more architecture than most language pairs on this site, and diverge in specific, namable places. This chapter starts with what they share.

The CLR vs. the JVM

C# compiles to IL (Intermediate Language) โ€” not directly to machine code โ€” and the CLR (Common Language Runtime) JIT-compiles that IL at runtime, exactly the same two-stage model java1-1 described for the JVM and Java bytecode. This is a genuine point of similarity worth naming explicitly, not a coincidence โ€” C# inherited this architecture on purpose, the same "write once, run wherever the runtime exists" tradeoff Java made first.

The dotnet CLI

dotnet new console -o MyApp # scaffolds a new project dotnet run # compiles AND runs in one command dotnet build # compiles only, without running

Where java1-1 used two separate tools โ€” javac to compile, java to run โ€” dotnet is one unified CLI covering project scaffolding, building, and running. dotnet run alone does what javac + java together did.

Top-Level Statements

// Program.cs โ€” the entire program, no class or Main required Console.WriteLine("Hello, C#!");

Since C# 9, a file can skip the class-and-Main boilerplate entirely for the simplest programs โ€” a real, direct contrast to java1-1's strict "everything lives in a class, with an explicit public static void Main" requirement. Under the hood, though, the compiler still generates that exact class-and-Main shape automatically โ€” this is real syntactic sugar over the same model, not a fundamentally different one.

What's Really There

class Program { static void Main(string[] args) { Console.WriteLine("Hello, C#!"); // what the top-level version compiles down to } }

This is the explicit, traditional form โ€” the same shape java1-1 required from the very first line. Larger, real applications generally still write Main explicitly; top-level statements are mainly a convenience for small programs, scripts, and quick experiments.

C#'s Origin as Microsoft's Direct Answer to Java

C# was created in 2000, led by Anders Hejlsberg โ€” previously the architect behind Turbo Pascal and Delphi, later the designer of TypeScript. Its creation followed directly from a Sun Microsystems lawsuit over Microsoft's own non-compliant Java implementation, J++ โ€” rather than continue fighting over Java itself, Microsoft built its own language and runtime from scratch. This is real, documented history, not just a marketing framing โ€” and it's exactly why C# and Java's architectures line up as closely as they do.

AspectJava (java1-1)C#
RuntimeJVMCLR
Intermediate formatbytecodeIL
Compile & run toolingjavac, then java โ€” two toolsdotnet โ€” one unified CLI
Minimum program shapealways an explicit class + maintop-level statements allowed (C# 9+)
Top-level statements suit scripts, not necessarily large apps
Reach for top-level statements for small programs, quick experiments, and this course's own early examples โ€” real multi-file applications generally still write Main explicitly once there's more than one entry point's worth of setup to reason about.
Only one file per project may use top-level statements
A project can have exactly one file containing top-level statements โ€” attempting a second produces a real compile error, since the compiler can only generate one implicit Main per project.

Coding Challenges

Challenge 1

Create a new console project with dotnet new console, write a top-level-statement Program.cs that prints two lines of your choosing, and run it with dotnet run.

๐Ÿ“„ View solution
Challenge 2

Rewrite Challenge 1's program using the explicit class Program with a static void Main(string[] args) method instead of top-level statements, and confirm it produces identical output.

๐Ÿ“„ View solution
Challenge 3

Explain in a comment why C# and Java both use a two-stage compile-then-JIT model rather than compiling directly to native machine code, tying your answer to C#'s own origin as a direct answer to Java.

๐Ÿ“„ View solution

Chapter 1 Quick Reference

  • The CLR JIT-compiles IL at runtime โ€” the same two-stage model as the JVM and Java bytecode (java1-1)
  • dotnet is one unified CLI for scaffolding, building, and running โ€” Java needed javac and java separately
  • Top-level statements (C# 9+) skip the class/Main boilerplate for simple programs โ€” real sugar, not a different model underneath
  • Only one file per project may use top-level statements
  • C# was created in 2000 as Microsoft's direct answer to Java, led by Anders Hejlsberg โ€” later also the designer of TypeScript
  • Next chapter: variables and basic types โ€” value types vs. reference types, and var type inference
Chapter 2 of 8

Variables & Basic Types

Course 1 ยท Ch 2
Variables & Basic Types
Java draws the primitive/reference line for you โ€” C# hands you the pen

java1-2 drew a hard line: eight built-in primitives, everything else a reference type, no way for a programmer to add to either side. C# keeps the same two-category split โ€” but genuinely lets you choose which side a new type belongs on.

Value Types vs. Reference Types

A value type holds its data directly โ€” assigning or passing it copies the actual data. A reference type holds a reference to data stored elsewhere โ€” assigning or passing it copies the reference, not the underlying data. This is C#'s own version of java1-2's primitive-vs-reference split, but with a genuine structural difference: in Java, only the eight built-in primitives are ever value-like โ€” every programmer-defined type is automatically a reference type, no exceptions. In C#, a programmer can define a brand-new value type of their own.

struct vs. class

struct Point { // a value type โ€” copied by value public int X, Y; } class Person { // a reference type โ€” copied by reference, same as every java1-4 class public string Name; }

struct declares a value type; class declares a reference type โ€” otherwise, the two look almost identical to write. This is the real, structural choice java1-4's own classes never offered: in Java, every custom type is unconditionally a reference type, full stop.

Built-in Types Are Just Structs

int x = 5; x.ToString(); // legal โ€” int genuinely IS a struct (System.Int32), a real object underneath 5.ToString(); // also legal, for the same reason

Here's the real reveal: int, double, and bool aren't magic keywords divorced from the type system โ€” they're built-in aliases for real structs (System.Int32, System.Double, System.Boolean). Because they're genuinely structs, they can call methods directly, no wrapping required. This is a real, structural contrast with java1-2's own primitives, which aren't objects at all โ€” Java's int can never call a method on itself; it must first be boxed into an Integer.

var Type Inference

var count = 5; // inferred as int at compile time var name = "Alice"; // inferred as string at compile time // count = "text"; // still a compile error โ€” var is NOT dynamic typing

var lets the compiler infer a variable's type from its initializer โ€” the type is fixed permanently at compile time, exactly as if it had been spelled out explicitly. This is genuinely still static typing, in the same spirit as C++'s auto, not a loophole into dynamic typing.

Nullable Value Types

int count = null; // compile error โ€” a plain value type can never be null int? count = null; // legal โ€” opts in via Nullable<int> underneath

A value type genuinely can't be null by default โ€” it's real data sitting directly in the variable, not a reference that could point to nothing. The ? suffix opts a value type into nullability through Nullable<T>, a wrapper struct. This is a separate, earlier feature from Course 2's own nullable reference types chapter, which addresses the opposite problem โ€” reference types, which could always be null by default until that later feature opted them out of it.

AspectJava (java1-2)C#
Who can define a value typenobody โ€” only 8 built-insany programmer, via struct
Is int a real object?no โ€” must box to Integer firstyes โ€” int genuinely is System.Int32
Calling a method on a literalnot possible directly5.ToString() works directly
Can a value type be nulln/a โ€” primitives have no null conceptonly if explicitly marked nullable (int?)
Reach for struct for small, immutable, frequently-copied data
A small value type like a coordinate pair avoids heap allocation and garbage-collector pressure entirely โ€” a genuine performance-relevant choice, not just a style preference, when a type is small and copied often.
A large struct copied often can hurt, not help, performance
Value-type semantics mean a struct is copied in full on every assignment and every parameter pass โ€” for a large struct, that copying cost can exceed the cost of copying a reference to a class instance. struct is a genuine tradeoff, not a free performance win in every case.

Coding Challenges

Challenge 1

Write a struct Point with X and Y int fields, and a class Wallet with a decimal Balance field, create one instance of each, assign each to a second variable, modify the second variable's fields, and print both original and copy to show which one changed and why.

๐Ÿ“„ View solution
Challenge 2

Write code that calls a method directly on an int literal (e.g. 42.ToString()) and explain in a comment why this is legal in C# but would not be legal on a raw int in Java without boxing.

๐Ÿ“„ View solution
Challenge 3

Declare an int? variable, assign it null, then attempt the same with a plain int. Show the resulting compile error on the plain int and explain the difference in terms of value-type semantics.

๐Ÿ“„ View solution

Chapter 2 Quick Reference

  • struct declares a value type (copied by value); class declares a reference type (copied by reference) โ€” unlike Java, where only 8 built-ins are ever value-like
  • C#'s int/double/bool are real structs (System.Int32, etc.) โ€” they can call methods directly, unlike Java's non-object primitives
  • var infers a type at compile time โ€” still fully static typing, not dynamic typing
  • A plain value type can never be null; int? opts in via the Nullable<T> wrapper struct
  • struct is a genuine tradeoff โ€” great for small, immutable, frequently-copied data, costly for large types copied often
  • Next chapter: operators and control flow โ€” switch expressions with pattern matching from day one
Chapter 3 of 8

Operators & Control Flow

Course 1 ยท Ch 3
Operators & Control Flow
Fallthrough forbidden by default โ€” the opposite of C and Java's own switch default

Most of C#'s operators are shared syntax with every C-family language already covered on this site. The genuine differences are concentrated in one place: switch, and two operators Java has no direct equivalent for at all.

Standard Operators & Integer Division

Arithmetic, relational, and logical operators behave exactly as in C/C++/Java. Integer division still truncates toward zero โ€” the same behavior c1-3 and java1-3 already covered, inherited unchanged.

switch Statements โ€” Fallthrough Forbidden by Default

switch (day) { case 1: Console.WriteLine("Monday"); // no break here โ€” this is a COMPILE ERROR in C#, not a silent bug case 2: Console.WriteLine("Tuesday"); break; }

This is a genuine, real difference worth stating plainly: C#'s switch statement forbids implicit fallthrough between non-empty case blocks โ€” the exact opposite default of c1-3's C and java1-3's Java. A case with a statement body must end in break, return, throw, or an explicit goto case โ€” omitting all four is a compile error, not a bug waiting to happen at runtime. Stacked empty case labels (case 1: case 2: DoSomething(); break;) are still fine, since neither label has its own body to fall out of.

switch Expressions โ€” Pattern Matching Since Day One

string result = day switch { 1 or 2 or 3 => "Early week", 6 or 7 => "Weekend", _ => "Other" // the discard pattern โ€” C#'s default case };

C# 8 (2019) shipped switch expressions with real pattern matching already built in โ€” genuinely before java1-3's own arrow-based switch expressions landed in Java 14 (2020). Another "C# arrived first" moment, alongside Course 2's own records chapter.

Pattern Matching in switch Expressions

string Describe(object obj) => obj switch { int n when n > 0 => "a positive int", // type pattern + relational guard string s when s.Length == 0 => "an empty string", null => "null", _ => "something else" };

Type patterns, relational guards via when, and a dedicated null pattern were all present from C# 8's initial launch โ€” a notably richer starting point than java1-3's own simpler initial arrow-only form.

Null-Conditional and Null-Coalescing Operators

string city = customer?.Address?.City; // ?. โ€” short-circuits to null the instant anything is null string display = city ?? "Unknown"; // ?? โ€” supplies a default only if the left side is null

?. (null-conditional) short-circuits an entire chain to null the moment any link is null, instead of throwing. ?? (null-coalescing) supplies a fallback value only when the left-hand side is null. Neither operator has a direct Java equivalent โ€” Java requires either manual null checks or an Optional-based rewrite to express the same idea.

FeatureC (c1-3)Java (java1-3)C#
switch statement fallthroughallowed by defaultallowed by defaultforbidden โ€” compile error
switch expression arrivaln/aJava 14 (2020)C# 8 (2019) โ€” first
Null-safe chaining operatornonenone โ€” manual checks or Optional?. and ??
Chain ?. and ?? together for concise null-safe reads
customer?.Address?.City ?? "Unknown" reads as one concise, self-documenting expression โ€” short-circuit through any null link, then supply a fallback โ€” replacing several lines of nested null checks in one line.
C# catches the missing-break bug at compile time, not runtime
Where c1-3 and java1-3 both warned that a missing break silently falls through into the next case, C#'s compiler simply refuses to build code shaped like that in the first place โ€” a real, structural safety improvement over both languages' own default.

Coding Challenges

Challenge 1

Write a switch statement over an int month (1-12) with a case that deliberately omits a break after a non-empty body. Show the resulting compile error, then fix it.

๐Ÿ“„ View solution
Challenge 2

Write a switch expression over an object using type patterns to distinguish int, string, and null, each returning a different descriptive string, with a discard pattern as the final case.

๐Ÿ“„ View solution
Challenge 3

Write a class hierarchy of at least two levels deep (e.g. a Customer with a nullable Address property, which itself has a nullable City property), and use ?. chained with ?? to safely read the city with a default fallback, without any explicit null checks.

๐Ÿ“„ View solution

Chapter 3 Quick Reference

  • switch statements forbid implicit fallthrough between non-empty cases โ€” a compile error, the opposite default of C (c1-3) and Java (java1-3)
  • switch expressions with pattern matching shipped in C# 8 (2019), before Java's own arrow-based version in Java 14 (2020)
  • Type patterns, relational when guards, and a null pattern were all present in C#'s switch expressions from the start
  • ?. (null-conditional) and ?? (null-coalescing) have no direct Java equivalent โ€” chain them for concise null-safe reads
  • Next chapter: classes and objects โ€” auto-implemented properties as a first-class language feature
Chapter 4 of 8

Classes & Objects

Course 1 ยท Ch 4
Classes & Objects
Properties are a real language feature here, not a naming convention java1-4 left to discipline

Constructors and fields work almost identically to java1-4. The real divergence is in how C# handles the getter/setter pattern every Java class relies on โ€” it's not a pattern here at all.

Constructors โ€” Quick Recap

class Account { private string _owner; public Account(string owner) { _owner = owner; } // same shape as java1-4 }

Same idea, same shape as java1-4's own constructors โ€” a method sharing the class's name, no return type, run once via new.

Properties โ€” A First-Class Language Feature

public class Customer { public string Name { get; set; } // auto-implemented property โ€” one line } Customer c = new Customer(); c.Name = "Alice"; // looks like direct field access... Console.WriteLine(c.Name); // ...but is really calling get/set underneath

In Java, "a property" is purely a naming convention โ€” a private field plus a hand-written getName()/setName() pair, entirely a matter of programmer discipline that java1-4's own language rules never enforce or even recognize. C# has real property syntax built directly into the language: { get; set; } alone generates a hidden backing field and both accessors automatically โ€” one line replaces what Java requires spelling out as three separate members.

Full Property Syntax with Custom Logic

private string _name; public string Name { get => _name; set => _name = value.Trim(); // real logic, still called like plain field access }

A property can run genuine logic in its accessors โ€” validation, transformation, computed values โ€” while the call site still reads exactly like ordinary field access: customer.Name = " Alice "; silently runs the trim logic. This is a real, different call-site ergonomic from Java's explicit setName(...) method call, which always visibly announces itself as a method call.

Read-Only and Init-Only Properties

public string Id { get; } // read-only โ€” settable only inside the constructor public string Sku { get; init; } // init-only (C# 9) โ€” settable once, at construction var product = new Product { Sku = "A1" }; // object-initializer syntax works with init

A get-only property can only be assigned inside the constructor. init (C# 9) is slightly more flexible โ€” it allows object-initializer syntax to set the property exactly once, at construction time, then locks it โ€” the same mechanism Course 2's own records chapter builds on internally for its immutable, boilerplate-free data carriers.

Access Modifiers, Revisited

C# has the same public/private/protected plus a genuine fourth level โ€” but a structurally different one than java1-4's package-private. C#'s internal means visible anywhere within the same assembly (a compiled project/DLL), a unit defined by how the project is built, not by folder or namespace structure the way Java's package-private is tied to the package a file physically sits in.

AspectJava (java1-4)C#
Getter/setter patterna naming convention onlyreal language syntax โ€” get/set
Call site for reading a valuecustomer.getName() โ€” visibly a method callcustomer.Name โ€” looks like field access
The "fourth" access levelpackage-private (no modifier) โ€” folder/package-scopedinternal โ€” assembly-scoped
Immutable-after-construction fieldfinal field, set once in constructorinit-only property, set once via initializer syntax
Default to auto-implemented properties
{ get; set; } alone covers the overwhelming majority of cases โ€” only expand to a full accessor body with explicit logic once a property genuinely needs validation, transformation, or a computed value.
A property that looks like a field can hide real, possibly expensive logic
Because a property's call site is indistinguishable from plain field access, a getter or setter with genuine work inside it โ€” validation, a database call, anything non-trivial โ€” is invisible at the call site in a way a Java method call never is. Keep property accessors cheap and side-effect-free as a matter of discipline, since nothing in the syntax itself signals otherwise.

Coding Challenges

Challenge 1

Write a class Rectangle with auto-implemented Width and Height properties, and a read-only Area property computed via an expression-bodied get that multiplies them.

๐Ÿ“„ View solution
Challenge 2

Write a class Customer with a Name property whose setter trims whitespace and rejects an empty string by throwing an exception, then demonstrate assigning " Alice " and reading back the trimmed result.

๐Ÿ“„ View solution
Challenge 3

Write a class Product with an init-only Sku property, construct an instance using object-initializer syntax, then attempt to reassign Sku afterward and show the resulting compile error.

๐Ÿ“„ View solution

Chapter 4 Quick Reference

  • Properties are real language syntax ({ get; set; }) โ€” Java's getter/setter is purely a naming convention, per java1-4
  • A property call site (customer.Name) looks like field access but can run real logic underneath, unlike Java's visibly-a-method-call getName()
  • init (C# 9) allows object-initializer syntax to set a property exactly once, at construction โ€” the mechanism Course 2's records build on
  • internal is C#'s own fourth access level โ€” assembly-scoped, structurally different from java1-4's folder/package-scoped package-private
  • Next chapter: inheritance and polymorphism โ€” virtual/override required explicitly, the reverse of java1-5's virtual-by-default
Chapter 5 of 8

Inheritance & Polymorphism

Course 1 ยท Ch 5
Inheritance & Polymorphism
The exact reverse of java1-5's virtual-by-default dispatch โ€” and a gotcha the compiler only warns about

This is the chapter where C# and Java's shared architecture stops predicting shared behavior. java1-5 made every instance method virtual unless told otherwise. C# does the opposite.

base and Inheritance Basics

class Animal { protected string Name; public Animal(string name) { Name = name; } } class Dog : Animal { // : instead of extends public Dog(string name) : base(name) {} // base(...) instead of super(...) }

Same underlying idea as java1-5, different keywords โ€” single inheritance via :, a base constructor call via base(...).

virtual and override โ€” Required Explicitly

class Animal { public virtual string Speak() => "..."; // must opt IN to being overridable } class Dog : Animal { public override string Speak() => "Woof"; // must opt IN to overriding it } Animal a = new Dog(); a.Speak(); // "Woof" โ€” dynamic dispatch, because BOTH keywords were present

Here's the real reversal: in C#, a method is not dynamically dispatched by default. java1-5 established that every Java instance method is virtual unless explicitly marked final/private/static. C# requires the base class to mark a method virtual and the derived class to mark its own version override โ€” without both keywords present, calling that method through a base-typed reference always runs the base class's own version, regardless of the object's real runtime type.

What Happens Without virtual/override โ€” Method Hiding

class Animal { public string Speak() => "..."; // NOT virtual } class Dog : Animal { public new string Speak() => "Woof"; // hides, does not override } Animal a = new Dog(); a.Speak(); // "..." โ€” Animal's own version, based on the DECLARED type ((Dog)a).Speak(); // "Woof" โ€” only when accessed through the Dog-typed reference

Redeclaring a method with the same signature but no override creates an entirely different, non-polymorphic mechanism called method hiding โ€” the new keyword makes this explicit. Which version runs depends on the reference's declared type, not the object's real runtime type โ€” the opposite of what java1-5 trained every reader to expect from an object hierarchy.

Why C# Requires Opt-In Virtual

Two real, deliberate reasons, not accidents of history: a non-virtual method call can be resolved at compile time, avoiding the runtime dispatch-table lookup a virtual call requires โ€” a genuine performance difference at scale. And a base class author is forced to explicitly decide, and effectively document, which methods are meant to be extension points โ€” rather than every method automatically becoming one whether the original author intended it or not, as java1-5's virtual-by-default makes true for every Java class.

sealed โ€” Preventing Further Overriding

public override sealed string Speak() => "Woof"; // no further class may override THIS override public sealed class FinalBreed : Dog {} // prevents inheritance from this class entirely

sealed override stops a further subclass from overriding an already-overridden method; sealed class prevents any further inheritance at all โ€” genuinely comparable to Kotlin's own classes being closed to inheritance by default, an ironic point of alignment given C# otherwise opts into virtual dispatch the same non-default way Kotlin does.

AspectJava (java1-5)C#
Dispatch defaultvirtual by defaultnon-virtual by default
To enable dynamic dispatchnothing needed โ€” always onvirtual (base) + override (derived), both required
Redeclaring without opting inn/a โ€” always overridesmethod hiding โ€” a different, non-polymorphic mechanism
Compile-time feedback if forgottenn/aa warning (CS0114), not an error
Mark virtual only when a method is genuinely meant to be extended
C#'s opt-in model exists specifically so a base class's author states real intent โ€” reach for virtual deliberately, as a design decision, not as a default habit carried over from Java.
Forgetting override is a warning, not an error โ€” the code still runs
Redeclaring a base method's signature without override or new compiles successfully with only a CS0114 warning ("hides inherited member") โ€” the program builds and runs, silently producing method-hiding behavior instead of the polymorphism the programmer likely intended. This is genuinely easy to miss if warnings aren't being read.

Coding Challenges

Challenge 1

Write a Shape base class with a virtual Area() method returning 0, and a Circle subclass that overrides it correctly. Store the Circle in a Shape-typed variable and call Area(), showing Circle's version runs.

๐Ÿ“„ View solution
Challenge 2

Repeat Challenge 1, but make Shape's Area() NOT virtual, and have Circle redeclare it with new instead of override. Call Area() through a Shape-typed variable and through a Circle-typed variable, showing the two different results.

๐Ÿ“„ View solution
Challenge 3

Write Challenge 2's Circle.Area() with neither override nor new (just a plain redeclaration), compile it, and report the exact CS0114 warning text produced, explaining why the code still compiles and runs despite it.

๐Ÿ“„ View solution

Chapter 5 Quick Reference

  • C# requires virtual (base) AND override (derived) for dynamic dispatch โ€” the exact reverse of java1-5's virtual-by-default
  • Redeclaring without override creates method hiding (new keyword) โ€” dispatch depends on the reference's declared type, not the object's real type
  • Opt-in virtual buys compile-time-resolvable calls by default and forces explicit intent about what's an extension point
  • sealed override stops further overriding; sealed class stops further inheritance entirely, echoing Kotlin's own closed-by-default classes
  • Forgetting override produces only a CS0114 warning, not a compile error โ€” the silent method-hiding gotcha still compiles and runs
  • Next chapter: interfaces and abstract classes โ€” default interface methods since C# 8, and explicit interface implementation
Chapter 6 of 8

Interfaces & Abstract Classes

Course 1 ยท Ch 6
Interfaces & Abstract Classes
A feature with no Java equivalent at all โ€” resolving a name collision without picking a winner

This time Java arrived first: java1-6's default interface methods shipped in Java 8 (2014), years before C# 8 (2019) added the same idea to C#. But C# didn't stop there โ€” it added something Java's interface system has no equivalent for at all.

Interfaces โ€” Recap

interface IMovable { void Move(double distance); // implicitly public โ€” no modifier written }

Same underlying idea as java1-6 โ€” a pure contract, a class implements ... except C# spells that keyword :, the same syntax used for class inheritance. Interface members are implicitly public, with no modifier needed.

Default Interface Methods (C# 8) โ€” Java Arrived First This Time

interface IMovable { void Move(double distance); string Stop() => "Stopping."; // a real default body, C# 8 (2019) }

C# 8 added default interface method bodies โ€” the identical idea java1-6 described for Java 8, five years earlier. Worth stating plainly, since it varies chapter to chapter: sometimes C# arrives first (switch expressions, records), and sometimes Java does, as here.

Multiple Interface Implementation โ€” The Diamond Problem, Again

interface Greeter { string Greet() => "Hello from Greeter"; } interface Welcomer { string Greet() => "Hello from Welcomer"; } class Host : Greeter, Welcomer { public string Greet() => ((Greeter)this).Greet(); // explicit resolution required, like java1-6 }

The same narrow diamond problem java1-6 covered resurfaces here โ€” two conflicting default implementations force an explicit resolution, or the class fails to compile. The syntax differs (a cast to the interface type, rather than Java's InterfaceName.super.method()), but the underlying requirement is identical: the compiler refuses to guess.

Explicit Interface Implementation โ€” No Java Equivalent

interface IPrintable { void Process(); } interface ISavable { void Process(); } class Document : IPrintable, ISavable { void IPrintable.Process() { Console.WriteLine("Printing..."); } // TWO separate implementations โ€” void ISavable.Process() { Console.WriteLine("Saving..."); } // no collision at all } Document doc = new Document(); ((IPrintable)doc).Process(); // "Printing..." ((ISavable)doc).Process(); // "Saving..." โ€” the SAME object, two distinct behaviors

Here's the genuine reveal: java1-6's own diamond-problem fix forces a single, explicit resolution โ€” one implementation wins. C#'s explicit interface implementation lets a class keep both conflicting implementations simultaneously, each one only reachable through the specific interface type it belongs to. This resolves a real name-collision scenario Java's interface system has no mechanism for at all โ€” Java would require renaming one of the two methods; C# doesn't.

Abstract Classes โ€” Quick Recap

abstract class Shape { public abstract double Area(); // no body โ€” subclasses must supply one public string Describe() => $"Area: {Area()}"; }

Same idea as java1-6's own abstract classes โ€” real state, constructors, and concrete methods alongside abstract ones, still limited to single inheritance.

FeatureJava (java1-6)C#
Default method bodiesJava 8 (2014) โ€” firstC# 8 (2019)
Conflicting defaults from two interfacesforced single explicit overrideforced single explicit resolution, same idea
Same method name, two distinct behaviorsnot possible โ€” must rename oneexplicit interface implementation โ€” both kept
Reach for explicit interface implementation for genuine name collisions
When two interfaces a class implements both need a method with the identical name but different meanings, explicit interface implementation keeps both behaviors distinguishable by which interface reference is used to call them โ€” no renaming required on either side.
An explicitly-implemented member isn't reachable through the class type
doc.Process() does not compile at all if Process() was only implemented explicitly for IPrintable/ISavable โ€” it's only reachable via a variable or cast of the specific interface type, never through the concrete class type directly. This is easy to be caught out by the first time it's encountered.

Coding Challenges

Challenge 1

Write an interface IGreetable with an abstract Hello() method and a default Bye() method, then a class implementing IGreetable that only supplies Hello(), demonstrating Bye() is inherited automatically.

๐Ÿ“„ View solution
Challenge 2

Write two interfaces IPrintable and ISavable that both declare a Process() method, and a Document class using explicit interface implementation to give each a genuinely different body. Call both through interface-typed casts of the same instance.

๐Ÿ“„ View solution
Challenge 3

Using Challenge 2's Document class, attempt to call doc.Process() directly on a Document-typed variable (not cast to either interface). Show the resulting compile error and explain why it happens.

๐Ÿ“„ View solution

Chapter 6 Quick Reference

  • Default interface methods shipped in C# 8 (2019) โ€” years after java1-6's own Java 8 (2014) version
  • Conflicting defaults from two interfaces still force explicit resolution, the same requirement java1-6 covered for Java
  • Explicit interface implementation lets one class keep two genuinely different same-named methods โ€” no Java equivalent exists
  • An explicitly-implemented member is reachable ONLY through the specific interface type, never through the concrete class type
  • Abstract classes work the same as java1-6's own โ€” state, constructors, and concrete methods alongside abstract ones, still single inheritance
  • Next chapter: exception handling โ€” every exception is unchecked, a real departure from java1-7's checked/unchecked split
Chapter 7 of 8

Exception Handling

Course 1 ยท Ch 7
Exception Handling
Every C# exception is unchecked โ€” a deliberate, documented rejection of java1-7's checked/unchecked split

java1-7 positioned Java's checked exceptions as a real middle ground between C++'s unenforced exceptions and Rust's Result<T, E>. C#'s designers looked at that same middle ground and deliberately rejected it โ€” this chapter explains why, with their own stated reasoning.

try/catch/finally โ€” Same Shape

try { int result = 10 / 0; } catch (DivideByZeroException e) { Console.WriteLine(e.Message); } finally { Console.WriteLine("Always runs"); }

Structurally identical to java1-7 โ€” try, a typed catch, an unconditional finally. The real divergence isn't the syntax.

Every C# Exception Is Unchecked

C# has no checked-exception concept at all. Every exception, without exception, behaves the way java1-7 described for RuntimeException โ€” a method can throw anything, with zero compiler-enforced acknowledgment, ever. No throws clause exists in C#'s syntax to require in the first place.

Why C# Rejected Checked Exceptions

This was a deliberate design decision, not an oversight โ€” Anders Hejlsberg (csharp1-1's own origin-story architect) has spoken publicly about the specific reasoning. Two real, documented critiques of java1-7's own model: checked exceptions don't version well โ€” adding one new checked exception to a widely-used method's signature breaks every existing caller that doesn't already handle it, a real practical cost in library evolution. And in practice, checked exceptions get "handled" via empty catch blocks or wrapped indiscriminately in a generic RuntimeException just to satisfy the compiler โ€” arguably making real code worse, not safer, than leaving the choice to the programmer's judgment.

XML Doc Comments as the Real (Weaker) Substitute

/// <summary>Withdraws funds from the account.</summary> /// <exception cref="InsufficientFundsException">Thrown when balance is too low.</exception> public void Withdraw(decimal amount) { /* ... */ }

C# lets an API author document what a method might throw via an XML doc comment's <exception> tag, surfaced in IntelliSense โ€” but nothing here is compiler-enforced at all. It's purely informational, a real, direct contrast against java1-7's own compiler-enforced throws clause.

Exception Filters โ€” A Genuinely Unique Addition

try { ProcessOrder(); } catch (HttpRequestException e) when (e.StatusCode == 503) { Retry(); // only fires when BOTH the type AND the condition match } catch (HttpRequestException e) { LogAndFail(e); // every other HttpRequestException falls here }

catch (...) when (condition) is a real feature with no direct Java equivalent โ€” a catch block only fires when its exception type and a runtime condition both hold, avoiding a nested if statement (and a re-throw of everything that doesn't match) inside a broader catch.

using โ€” C#'s Answer to try-with-resources

using var reader = new StreamReader(path); // disposed automatically at the END of the enclosing scope Console.WriteLine(reader.ReadLine()); // no closing brace needed โ€” this is C# 8's using DECLARATION, more concise than java1-7's try(...)

Any type implementing IDisposable works with using, calling Dispose() automatically โ€” genuinely comparable to java1-7's try-with-resources/AutoCloseable, and to cpp1-5's RAII. C# 8's using declaration form (shown above, no extra braces) is even more concise syntactically than Java's own block-scoped try(...) syntax.

AspectJava (java1-7)C#
Checked exceptionsyes โ€” compiler-enforced catch or throwsno such concept exists
Documenting what a method might throwthrows clause โ€” compiler-enforcedXML doc <exception> tag โ€” informational only
Conditional catchnot built in โ€” needs a nested ifcatch (...) when (condition) โ€” built in
Automatic resource cleanuptry-with-resourcesusing โ€” including a more concise declaration form
Reach for exception filters instead of re-throwing inside a broad catch
catch (...) when (condition) keeps a narrow, conditional handling case cleanly separate from the general case, without needing an inner if and a manual re-throw to fall through to a second catch block.
Nothing forces you to consider a realistic failure mode
Because C# has no checked-exception mechanism, the compiler never prompts a caller to think about what a method might throw โ€” java1-7's own compiler-enforced acknowledgment simply has no C# counterpart. Knowing what a method can throw relies entirely on documentation, testing, and discipline, not language enforcement.

Coding Challenges

Challenge 1

Write a method that divides by zero inside a try block, catch DivideByZeroException specifically, and use a finally block that always runs. Then write a second method that throws a custom exception with no throws-style declaration anywhere, showing it compiles without any compiler warning at all.

๐Ÿ“„ View solution
Challenge 2

Write a method that throws a custom exception with an int ErrorCode property, then use two catch blocks: one with a when filter that only fires for a specific error code, and a second, unfiltered catch for every other case.

๐Ÿ“„ View solution
Challenge 3

Write a class implementing IDisposable with a Dispose() method that prints a message, then use it with a using declaration (not a using block) alongside code that throws an exception afterward in the same scope, showing Dispose() still runs.

๐Ÿ“„ View solution

Chapter 7 Quick Reference

  • C# has no checked-exception concept โ€” every exception behaves like java1-7's own RuntimeException, with zero compiler-enforced acknowledgment
  • This was a deliberate, documented design choice (Anders Hejlsberg) โ€” checked exceptions version poorly and get swallowed via empty catches/blanket rethrows in practice
  • XML doc <exception> tags document possible throws for IntelliSense, but enforce nothing at compile time, unlike Java's throws clause
  • catch (...) when (condition) โ€” exception filters with no direct Java equivalent
  • using (including C# 8's more concise using declaration) is C#'s IDisposable-based answer to try-with-resources
  • Next chapter: collections and a first taste of LINQ, previewing Course 2's own deep dive
Chapter 8 of 8

Collections & a First Taste of LINQ

Course 1 ยท Ch 8
Collections & a First Taste of LINQ
C# had this idea in 2007 โ€” seven years before java2-4's own Streams API

Fundamentals closes with C#'s everyday collections โ€” and a first look at the feature that touches nearly every one of them, deferred to Course 2's own full chapter, but too central to leave out entirely here.

List<T> โ€” A Growable Collection

List<string> names = new(); names.Add("Alice"); names.Add("Bob");

Nearly identical in behavior to java1-8's own List/ArrayList โ€” grows automatically, generic. C#'s generics are reified rather than erased, a real difference Course 2's own Generics In Depth chapter covers in full; this chapter stays at the surface level.

Dictionary<TKey, TValue>

Dictionary<string, int> ages = new(); ages["Alice"] = 30; // indexer syntax โ€” no .put() call needed Console.WriteLine(ages["Alice"]);

Comparable to java1-8/java2-2's own Map/HashMap โ€” key-value pairs, average constant-time lookup. C#'s indexer syntax (ages["Alice"]) reads slightly more concisely than Java's explicit .put()/.get() calls, though the underlying idea is identical.

Iterating โ€” foreach

foreach (var name in names) { Console.WriteLine(name); }

Directly comparable to Java's own enhanced for loop โ€” same purpose, different keyword.

A First Taste of LINQ

int[] numbers = { 1, 2, 3, 4, 5, 6 }; // method syntax var evenSquares = numbers.Where(n => n % 2 == 0).Select(n => n * n); // query syntax โ€” SQL-like, built directly into the language grammar var evenSquares2 = from n in numbers where n % 2 == 0 select n * n;

LINQ โ€” Language Integrated Query โ€” shipped in C# 3.0, back in 2007, genuinely comparable to java2-4's own Streams API but arriving seven years earlier. Another "C# first" moment, like csharp1-3's own switch expressions. LINQ offers two syntaxes for the same underlying query: fluent method chaining (.Where().Select()), and a genuinely unique SQL-like query syntax baked directly into C#'s own grammar โ€” something neither Java nor most other languages on this site offer as native syntax at all.

Why Preview LINQ Now

LINQ works over anything implementing IEnumerable<T> โ€” which includes List<T>, Dictionary<TKey,TValue>, arrays, and every collection this chapter just introduced. That's exactly why it belongs here rather than waiting entirely for Course 2 โ€” it's the natural next step for everything just covered, not a separate, unrelated topic.

AspectJava Streams (java2-4)C# LINQ
ArrivalJava 8 (2014)C# 3.0 (2007) โ€” first
Fluent chaining.filter().map().collect().Where().Select()
SQL-like query syntaxnot availablefrom...where...select, built into the grammar
Method syntax composes; query syntax reads naturally for SQL-like filtering
Method syntax chains cleanly with everything else in C#; query syntax often reads more naturally for filtering/joining operations that already feel SQL-like. Course 2's own LINQ In Depth chapter covers when to reach for each.
This is only a first taste โ€” the real depth is Course 2's
Deferred execution, the difference between IEnumerable<T> and IQueryable<T>, and LINQ's full operator vocabulary are all Course 2 material โ€” this chapter only establishes that LINQ exists and roughly what it looks like.

Coding Challenges

Challenge 1

Create a List<int> of at least six numbers, then use LINQ method syntax to filter for numbers greater than 10 and print the result using foreach.

๐Ÿ“„ View solution
Challenge 2

Create a Dictionary<string, int> of at least four name/age pairs, then write the same query twice โ€” once using LINQ method syntax and once using query syntax โ€” to find everyone with age 30 or older, printing both results to show they match.

๐Ÿ“„ View solution
Challenge 3

Write a short comment explaining why LINQ can operate over a List<T>, a Dictionary<TKey,TValue>, and a plain array all with the same syntax, referencing IEnumerable<T> directly.

๐Ÿ“„ View solution

Chapter 8 Quick Reference โ€” Course 1 Complete

  • List<T> and Dictionary<TKey,TValue> behave comparably to java1-8/java2-2's List and Map
  • foreach is directly comparable to Java's enhanced for loop
  • LINQ shipped in C# 3.0 (2007), seven years before java2-4's own Streams API
  • LINQ offers both fluent method syntax and a genuinely unique SQL-like query syntax built into the language grammar
  • LINQ works over anything implementing IEnumerable<T> โ€” lists, dictionaries, and arrays alike
  • C# Fundamentals is now complete. Course 2 (Intermediate/Advanced) begins with Generics In Depth โ€” C#'s reified generics, a genuine opposite design choice from java2-1's own type erasure.