C# Basics
From Application Code to Native Machine Code in .NET
"C# (or other .NET languages) → IL → CLR → Native machine code"
- You write application code in C#, F#, or VB.NET.
- The code is compiled by the language compiler into IL (Intermediate Language), a CPU-independent set of instructions.
- The CLR (Common Language Runtime) executes IL at runtime. It uses the JIT (Just-In-Time) compiler to translate IL into native machine code, optimized for your platform.
- The result: your .NET application runs as fast, optimized native code on your OS.
Interview line: "A .NET program is compiled from source code to IL, which is then JIT-compiled by the CLR into native machine code for execution."
CLR (Common Language Runtime)
"CLR = runtime that executes .NET code"
-
JIT (Just-In-Time) — compiles IL to native code at runtime
-
Garbage collection — manages heap memory
-
Type system — enforces type safety
-
Assembly loading — loads and runs .NET assemblies
Interview line: "CLR is the execution engine. It JIT-compiles IL, runs GC, and enforces the type system."
Input and Output
Console I/O
// Input
string line = Console.ReadLine();
int n = int.Parse(Console.ReadLine());
// Output
Console.WriteLine("Hello");
Console.Write("No newline");
Console.WriteLine($"Value: {n}");
File I/O
// Read
string text = File.ReadAllText("file.txt");
string[] lines = File.ReadAllLines("file.txt");
// Write
File.WriteAllText("file.txt", "content");
File.WriteAllLines("file.txt", lines);
// Async
await File.ReadAllTextAsync("file.txt");
Variables: Value vs Reference Types
"Value = copy on assign; Reference = same object"
| Type | Stored | Examples |
|---|---|---|
| Value | Stack (usually) | int, bool, struct, enum |
| Reference | Heap, variable on stack holds reference | class, string, array, delegate |
Primitive / built-in value types
int,long,short,bytefloat,double,decimalbool,char
enum
public enum Status { Pending, Active, Done }
Status s = Status.Active;
int i = (int)s; // 1
struct (value type)
public struct Point
{
public int X, Y;
}
Point p; // stack; default 0,0
Nullable value types
int? n = null;
bool hasValue = n.HasValue;
int value = n ?? 0;
Interview line: "Value types live on the stack (or inside objects on the heap). Reference types live on the heap; the variable holds a reference."
Conversion: Explicit, Implicit, Parse, TryParse
Implicit (no cast, safe)
int i = 42;
long l = i;
Explicit (cast, may lose data)
double d = 3.14;
int i = (int)d; // 3
Parse (throws on failure)
int i = int.Parse("123");
// throws FormatException if invalid
TryParse (safe, returns bool)
if (int.TryParse("123", out int result))
Console.WriteLine(result);
Convert class
int i = Convert.ToInt32("42");
// throws on null or invalid
Interview line: "Use TryParse for user input; use Parse when you expect valid data. Convert offers various conversions but throws on failure."
Operations
- Arithmetic:
+,-,*,/,% - Comparison:
==,!=,<,>,<=,>= - Logical:
&&,||,! - Bitwise:
&,|,^,~,<<,>> - Assignment:
=,+=,-=, etc. - Null coalescing:
??,??=
int a = 10, b = 3;
int q = a / b; // 3 (integer division)
double d = 10.0 / 3; // 3.333...
string s = name ?? "default";
Conditions and Loops
if / else
if (x > 0) {
} else if (x < 0) {
} else {
}
switch
switch (value)
{
case 1: break;
case 2: break;
default: break;
}
// switch expression (C# 8+)
var result = value switch { 1 => "One", 2 => "Two", _ => "Other" };
for / while / do-while
for (int i = 0; i < 10; i++) { }
while (condition) { }
do { } while (condition);
break / continue
- break — exit loop or switch
- continue — skip to next iteration
this and Extension Methods
this (current instance)
public void SetName(string name) { this.Name = name; }
Extension methods
"Add methods to existing types without subclassing"
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s);
}
// Usage: "".IsNullOrEmpty()
- Static class, static method
- First parameter:
this TypeName name
Fields vs Properties
| Fields | Properties |
|---|---|
| Direct storage | Get/set accessors |
public int x; | public int X { get; set; } |
| Avoid public fields | Preferred for public API |
private int _age;
public int Age
{
get => _age;
set => _age = value > 0 ? value : 0;
}
// Auto-implemented
public string Name { get; set; }
Access Modifiers
For types (class, struct, etc.)
- public — any assembly
- internal — same assembly only (default for non-nested types)
For members
- public — everywhere
- private — same class only (default for members)
- protected — same class + derived
- internal — same assembly
- protected internal — same assembly OR derived (any assembly)
static, const, readonly
| Keyword | Meaning | When set |
|---|---|---|
| static | One per type, not per instance | N/A |
| const | Compile-time constant | At declaration |
| readonly | Set once (field) | Constructor or declaration |
public static int Count;
public const int Max = 100;
public readonly DateTime Created = DateTime.UtcNow;
- static — shared by all instances; use for utilities, singletons
- const — value type or string only; inlined by compiler
- readonly — can be set in constructor; reference types: reference fixed, object can change
Overload vs Override
| Overload | Override |
|---|---|
| Same method name, different parameters (same class) | Replace base method in derived class |
| Compile-time | Runtime (polymorphism) |
| No keyword | override + base has virtual/abstract |
// Overload
void Print(int x) { }
void Print(string s) { }
// Override
public override string ToString() => $"{Name}";
Parameter Modifiers: ref, out, in, params
C# provides special parameter modifiers that affect how arguments are passed between the caller and the method. These are especially useful for working with multiple return values, performance, or flexible argument lists.
| Modifier | Meaning | Usage |
|---|---|---|
ref | Pass by reference. Must be initialized before. | Can read/write in method. Caller sees changes. |
out | Pass by reference. Must be assigned inside method (no need to init caller). | Caller receives value from method. |
in | Pass by reference as readonly (C# 7.2+). | For large structs when you want to avoid copying, but keep read-only. |
params | Variable number of arguments (array-like). | Method can accept any number of parameters as an array. |
Examples:
// ref example
void Increment(ref int x) { x++; }
int a = 2;
Increment(ref a); // a is now 3
// out example
bool TryParseInt(string s, out int result)
{
return int.TryParse(s, out result);
}
int n;
if (TryParseInt("42", out n)) { /* n==42 */ }
// in example (readonly ref)
void PrintLength(in string s) { Console.WriteLine(s.Length); }
// params example
int Sum(params int[] numbers) => numbers.Sum();
int total = Sum(1, 2, 3, 4); // total = 10
Tips:
- Both
refandoutarguments must be explicitly marked at both callsite and declaration. - Use
outfor "try to get value" or "multiple return"; usereffor bidirectional parameters. inis not for reference types (their reference is readonly, but object can still change!).paramsmust be the last parameter in the method signature.- Prefer minimizing use of
refandoutin everyday coding (consider returning tuples instead); but they're common in performance-critical code, interop, or legacy APIs.
namespace
"namespace = logical grouping; avoid name clashes"
namespace MyApp.Services
{
public class UserService { }
}
// Usage
using MyApp.Services;
// or
var s = new MyApp.Services.UserService();
- global using (C# 10) — apply to whole project
- File-scoped namespace (C# 10):
namespace MyApp;(no braces for file)
🧠 Ultra-Short Cheat Sheet
CLR = runtime (JIT, GC, type system)
Value = stack; Reference = heap
TryParse for input; Parse when valid
Extension method = static + this
Override = virtual/abstract + override
const = compile-time; readonly = once in ctor