C# Fundamentals
A Complete 8-Chapter Programming Course
Table of Contents
- Getting Started
- Variables & Basic Types
- Operators & Control Flow
- Classes & Objects
- Inheritance & Polymorphism
- Interfaces & Abstract Classes
- Exception Handling
- Collections & a First Taste of LINQ
Getting Started
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
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
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
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.
| Aspect | Java (java1-1) | C# |
|---|---|---|
| Runtime | JVM | CLR |
| Intermediate format | bytecode | IL |
| Compile & run tooling | javac, then java โ two tools | dotnet โ one unified CLI |
| Minimum program shape | always an explicit class + main | top-level statements allowed (C# 9+) |
Main explicitly once there's more than one entry point's worth of setup to reason about.
Main per project.
Coding Challenges
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 solutionRewrite 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 solutionExplain 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 solutionChapter 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
Variables & Basic Types
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 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
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 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
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.
| Aspect | Java (java1-2) | C# |
|---|---|---|
| Who can define a value type | nobody โ only 8 built-ins | any programmer, via struct |
| Is int a real object? | no โ must box to Integer first | yes โ int genuinely is System.Int32 |
| Calling a method on a literal | not possible directly | 5.ToString() works directly |
| Can a value type be null | n/a โ primitives have no null concept | only if explicitly marked nullable (int?) |
Coding Challenges
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 solutionWrite 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 solutionDeclare 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 solutionChapter 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
Operators & Control Flow
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
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
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
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
?. (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.
| Feature | C (c1-3) | Java (java1-3) | C# |
|---|---|---|---|
| switch statement fallthrough | allowed by default | allowed by default | forbidden โ compile error |
| switch expression arrival | n/a | Java 14 (2020) | C# 8 (2019) โ first |
| Null-safe chaining operator | none | none โ manual checks or Optional | ?. and ?? |
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.
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
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 solutionWrite 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 solutionWrite 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 solutionChapter 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
Classes & Objects
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
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
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
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
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.
| Aspect | Java (java1-4) | C# |
|---|---|---|
| Getter/setter pattern | a naming convention only | real language syntax โ get/set |
| Call site for reading a value | customer.getName() โ visibly a method call | customer.Name โ looks like field access |
| The "fourth" access level | package-private (no modifier) โ folder/package-scoped | internal โ assembly-scoped |
| Immutable-after-construction field | final field, set once in constructor | init-only property, set once via initializer syntax |
{ 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.
Coding Challenges
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 solutionWrite 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 solutionWrite 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 solutionChapter 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
Inheritance & Polymorphism
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
Same underlying idea as java1-5, different keywords โ single inheritance via :, a base constructor call via base(...).
virtual and override โ Required Explicitly
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
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
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.
| Aspect | Java (java1-5) | C# |
|---|---|---|
| Dispatch default | virtual by default | non-virtual by default |
| To enable dynamic dispatch | nothing needed โ always on | virtual (base) + override (derived), both required |
| Redeclaring without opting in | n/a โ always overrides | method hiding โ a different, non-polymorphic mechanism |
| Compile-time feedback if forgotten | n/a | a warning (CS0114), not an error |
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
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 solutionRepeat 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 solutionWrite 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 solutionChapter 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
Interfaces & Abstract Classes
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
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
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
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
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
Same idea as java1-6's own abstract classes โ real state, constructors, and concrete methods alongside abstract ones, still limited to single inheritance.
| Feature | Java (java1-6) | C# |
|---|---|---|
| Default method bodies | Java 8 (2014) โ first | C# 8 (2019) |
| Conflicting defaults from two interfaces | forced single explicit override | forced single explicit resolution, same idea |
| Same method name, two distinct behaviors | not possible โ must rename one | explicit interface implementation โ both kept |
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
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 solutionWrite 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 solutionUsing 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 solutionChapter 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
Exception Handling
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
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
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
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
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.
| Aspect | Java (java1-7) | C# |
|---|---|---|
| Checked exceptions | yes โ compiler-enforced catch or throws | no such concept exists |
| Documenting what a method might throw | throws clause โ compiler-enforced | XML doc <exception> tag โ informational only |
| Conditional catch | not built in โ needs a nested if | catch (...) when (condition) โ built in |
| Automatic resource cleanup | try-with-resources | using โ including a more concise declaration form |
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.
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
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 solutionWrite 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 solutionWrite 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 solutionChapter 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
Collections & a First Taste of LINQ
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
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>
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
Directly comparable to Java's own enhanced for loop โ same purpose, different keyword.
A First Taste of LINQ
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.
| Aspect | Java Streams (java2-4) | C# LINQ |
|---|---|---|
| Arrival | Java 8 (2014) | C# 3.0 (2007) โ first |
| Fluent chaining | .filter().map().collect() | .Where().Select() |
| SQL-like query syntax | not available | from...where...select, built into the grammar |
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
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 solutionCreate 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 solutionWrite 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 solutionChapter 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.