C# Essentials for Developers

 

1. What is C#?

 

C# is a computer programming language. C# was developed by Microsoft in 2000 to provide a modern general-purpose programming language that can be used to develop all kinds of software targeting various platforms including Windows, Web, and Mobile using just one programming language. Today, C# is one of the most popular programming languages in the world. Millions of software developers use C# to build all kinds of software. 

 

C# is the primary language for building Microsoft .NET software applications. Developers can build almost every kind of software using C# including Windows UI apps, console apps, backend services, cloud APIs, Web services, controls and libraries, serverless applications, Web applications, native iOS and Android apps, AI and machine learning software, and blockchain applications.

 

C# with the help of Visual Studio IDE provides rapid application development. C# is a modern, object-oriented, simple, versatile, and performance-oriented programming language. C# is developed based on the best features and use cases of several programming languages including C++, Java, Pascal, and SmallTalk. 

 

C# syntaxes are like C++. .NET and the C# library is similar to Java. C# supports modern object-oriented programming language features including Abstraction, Encapsulation, Polymorphism, and Inheritance. C# is a strongly typed language and most types are inherited by the Object class.

 

C# supports concepts of classes and objects. Classes have members such as fields, properties, events, and methods. Here is a detailed article on C# and OOP. 

 

C# is versatile, modern, and supports modern programming needs. Since its inception, C# language has gone through various upgrades. The latest version of C# is v12.0.


Lets Jump in..

2. Data types & Variables

C# Data Types :
C# is a strongly typed language, which means every variable must have a data type declared (explicitly/clearly or implicitly/not direct)

  • Value Types (stored on stack)- Store actual data.

    • int, double, float

    • bool, char

    • struct, enum

  • Reference Types (stored on heap)- Store reference (address) to data.

    • string, object, dynamic

    • Arrays (int[], string[])

    • Classes, Interfaces, Delegates

Data Type

Description

Example

int

32-bit integer

int x = 10;

double

Double-precision floating point

double pi = 3.14;

char

Single Unicode character

char c = 'A';

bool

Boolean value

bool isValid = true;

string

Sequence of characters

string name = "John";

var

Implicit typing (resolved at compile time)

var count = 5;

object

Base type of all types in C#

object o = 123;


Variables
A variable is a name that stores data that can change during program execution.

Declaring variables:

int age = 25;
string name = "Faizal";
bool isAdmin = false;


Naming Rules : 

Can contain letters, digits, and underscores.

Must start with a letter or _.

Cannot be a C# keyword (int, class, etc.).

Should be camelCase by convention for local variables.

User-defined Value Types

An enum is a special "value type" that lets you define a set of named constants.

It improves code readability and helps you avoid using magic numbers or hard-coded values.

enum Weekday
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

Behind the scenes, each name is mapped to an integer (starting from 0 by default).

Usage:

Weekday today = Weekday.Wednesday;

if (today == Weekday.Wednesday)
{
    Console.WriteLine("Midweek!");
}

Key Features

Feature

Notes

Default underlying type

int

Custom underlying type

Can be byte, short, etc.

You can set custom values

Yes


enum ErrorCode : byte
{
    None = 0,
    NotFound = 404,
    ServerError = 500
}

Common Uses

  • Status codes

  • Modes (e.g., ReadMode, WriteMode)

  • States (e.g., State.Idle, State.Running)

  • Days, categories, types, etc.

Converting Enums

int code = (int)ErrorCode.NotFound;  // 404
string name = ErrorCode.NotFound.ToString();  // "NotFound"

3.Operators


Operators are symbols used to perform operations on variables and values.

Arithmetic operators

Operator

Description

Example

+

Addition

a + b

-

Subtraction

a - b

*

Multiplication

a * b

/

Division

a / b

%

Modulus (remainder)

a % b


Assignment Operators

Operator

Example

Equivalent to

=

x = 5

Assigns 5 to x

+=

x += 2

x = x + 2

-=

x -= 2

x = x - 2


Comparison (Relational) Operators
Used in conditions (if, loops).

Operator

Description

Example

==

Equal to

a == b

!=

Not equal to

a != b

>

Greater than

a > b

<

Less than

a < b

>=

Greater or equal

a >= b

<=

Less or equal

a <= b


Logical Operators

Used to combine conditions.

Operator

Description

Example

&&

Logical AND

a > 0 && b < 5

`


`

!

Logical NOT

!(a == b)


Unary Operators

Operate on a single operand.

Operator

Description

Example

++

Increment

i++

--

Decrement

i--

+

Unary plus

+a

-

Unary minus

-a


Ternary Operator

A shorthand for if-else.


string result = (age > 18) ? "Adult" : "Minor";


Null-Coalescing Operators

Helps with null handling.


string name = null;
string finalName = name ?? "Default";


Example Combining All

int x = 10;
int y = 5;
bool isGreater = (x + y) > 12// true
string name = null;
string displayName = name ?? "Guest";

Console.WriteLine($"Sum: {x + y}, IsGreater: {isGreater}, User: {displayName}");


4. Control structures

Control structures in C# are flow control mechanisms that:

1. Conditional Statements 

if, else if, else

Used to execute code based on conditions.

int age = 20;

if (age < 13)
    Console.WriteLine("Child");
else if (age < 18)
    Console.WriteLine("Teen");
else
    Console.WriteLine("Adult");



Switch


string role = "admin";

switch (role)
{
    case "admin":
        Console.WriteLine("Welcome Admin!");
        break;
    case "user":
        Console.WriteLine("Welcome User!");
        break;
    default:
        Console.WriteLine("Unknown Role");
        break;
}


Use break to avoid falling through to the next case.

C# 8+ supports switch expressions for more concise syntax:

string message = role switch
{
    "admin" => "Welcome Admin!",
    "user" => "Welcome User!",
    _ => "Unknown Role"
};



2. Looping Statements

for loop

Used when the number of iterations is known.

for (int i = 0; i < 5; i++)
{
    Console.WriteLine($"Count: {i}");
}


while loop

Executes as long as the condition is true.

int i = 0;
while (i < 5)
{
    Console.WriteLine($"Value: {i}");
    i++;
}


do...while loop

Similar to while, but executes at least once.

int i = 0;
do
{
    Console.WriteLine($"Run: {i}");
    i++;
}
while (i < 3);


foreach loop

Used to iterate over collections like arrays, lists, etc.

string[] colors = { "Red", "Green", "Blue" };

foreach (string color in colors)
{
    Console.WriteLine(color);
}


3. Jump Statements (break, continue, return)

break

Exits the current loop or switch.

for (int i = 0; i < 10; i++)
{
    if (i == 5)
        break;
    Console.WriteLine(i);
}


continue

Skips the current iteration and continues the next one.

for (int i = 0; i < 5; i++)
{
    if (i == 2)
        continue;
    Console.WriteLine(i);  // Skips printing 2
}


return

Exits the method and optionally returns a value.

int Square(int n)
{
    return n * n;
}


5. Object oriented programming (OOP)

OOP is a programming paradigm that uses objects and classes to design software.

C# is a fully object-oriented language, so understanding OOP is essential for writing clean, modular, and maintainable code.

Classes and Objects

Class: Blueprint of an object.

Object: Instance of a class (created with new keyword).

class Person
{
    public string Name;
    public void Greet() => Console.WriteLine($"Hello, I'm {Name}");
}

Person p = new Person();
p.Name = "Faizal";
p.Greet();  // Hello, I'm Faizal




The 4 Pillars of OOP

Pillar

Description

Encapsulation

Hiding data and restricting access to internal state

Abstraction

Showing only essential features; hiding complexity

Inheritance

Reusing code via base/derived class relationships

Polymorphism

Having multiple forms — method overloading/overriding


Methods and Properties

A method is a block of code that performs a specific task. It belongs to a class and can return a value or be void.

public int Add(int a, int b)
{
    return a + b;
}


Types of Methods:

  • Instance Methods: Belong to an object (obj.Method()).

  • Static Methods: Belong to the class (Class.Method()).

Properties

A property is a class member that provides a flexible mechanism to read, write, or compute the value of a private field


public class Person
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}


Auto-Implemented Property:

C# allows you to write short-hand for simple get/set:

public string Email { get; set; }


You can also define read-only or write-only properties:

public int Age { get; private set; }   // Read-only from outside


Keywords in C#


Keyword

Purpose

static

Belongs to class, not instance

readonly

Field set once during construction or declaration

const

Compile-time constant

virtual

Allows method/property to be overridden

override

Overrides a base virtual method

abstract

Declares a method/property without body

sealed

Prevents inheritance or overriding

static

  • Used to declare members or methods that belong to the class, not instances.

  • Called on the class directly without new.


public static class MathUtils
{
    public static int Square(int n)
    {
        return n * n;
    }
}


int result = MathUtils.Square(5);  // No object needed


When to use:

  • Utility/helper classes

  • Shared data (e.g., counters, constants)

readonly

  • Applies to fields, ensures they can only be assigned:

    • at declaration, or

    • inside a constructor

public class Car
{
    public readonly int Wheels = 4;
}

const

  • Used for compile-time constants

  • Must be assigned immediately

public const double Pi = 3.14;


virtual, override, abstract, sealed (used in inheritance)

virtual

  • Allows a method or property to be overridden in derived class.

public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Some sound");
    }
}


override

  • Used in a derived class to override a base class method/property.

public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Bark!");
    }
}


abstract

  • Defines a method/property without implementation. Must be overridden in derived class.

  • Can only be declared in abstract classes.

public abstract class Shape
{
    public abstract double GetArea();  // No body
}

sealed

  • Prevents a class from being inherited, or a method from being overridden further.

public sealed class FinalClass { }  // Cannot be extended

public class Base
{
    public virtual void Log() { }

    public sealed override void Log() { }  // Cannot override further
}


Encapsulation

Encapsulation is the process of bundling data (fields) and methods that operate on the data, into a single unit (class), and restricting access using access modifiers.


class BankAccount
{
    private double balance;  // Private field -- hidden

    public void Deposit(double amount)
    {
        if (amount > 0)
            balance += amount;
    }

    public double GetBalance()
    {
        return balance;
    }
}

Access is controlled via public methods (Deposit, GetBalance), not directly.

Abstraction

Abstraction hides internal implementation and exposes only what is necessary

abstract class Animal
{
    public abstract void MakeSound(); // Abstract method
}

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Bark!");
    }
}


User just calls MakeSound(), not worrying how it’s implemented.

Inheritance

Inheritance allows a class (child) to inherit fields and methods from another class (parent).

class Vehicle
{
    public void Start() => Console.WriteLine("Engine started");
}

class Car : Vehicle
{
    public void Drive() => Console.WriteLine("Driving...");
}


Car c = new Car();
c.Start();  // Inherited
c.Drive();  // Own method

Polymorphism

Polymorphism means “many forms”. In C#, there are two types:

 a) Compile-Time Polymorphism (Method Overloading)

class Calculator
{
    public int Add(int a, int b) => a + b;
    public double Add(double a, double b) => a + b;
}


Same method name, different signatures.

b) Run-Time Polymorphism (Method Overriding using virtual / override)

class Animal
{
    public virtual void Speak() => Console.WriteLine("Some sound");
}

class Cat : Animal
{
    public override void Speak() => Console.WriteLine("Meow");
}


Animal myCat = new Cat();
myCat.Speak();  // Outputs: Meow


Which method runs is decided at runtime, depending on the actual object


Access Modifiers:


Modifier

Same Class

Derived Class (same assembly)

Same Assembly

Derived Class (other assembly)

Other Assembly

public

private

protected

internal

protected internal

private protected


public

  • The least restrictive modifier.

  • Members marked public can be accessed from any class, any namespace, any assembly.


public class Car
{
    public string Model;
}


Car car = new Car();
car.Model = "BMW"// OK


private (Default for class members)

  • Most restrictive.

  • Members are accessible only within the class where they are declared.


class Car
{
    private int speed;

    public void SetSpeed(int s)
    {
        speed = s;  // OK: accessed within the class
    }
}


Car car = new Car();
// car.speed = 100; ❌ Not allowed

protected

  • Accessible within the same class and its derived (child) classes.

  • Not accessible from outside unless you're inheriting.


class Vehicle
{
    protected int maxSpeed = 100;
}

class Bike : Vehicle
{
    public void ShowSpeed()
    {
        Console.WriteLine(maxSpeed); // OK: Inherited
    }
}


internal

  • Accessible anywhere within the same assembly/project.

  • Not accessible from another project (DLL).


internal class Invoice
{
    public void Print() => Console.WriteLine("Printing invoice...");
}


➡️ You can access this class from anywhere within the same project, but not from a referenced project.

protected internal

  • Accessible in derived classes anywhere OR within the same assembly.

  • Combines protected + internal.

protected internal int sharedValue;


➡️ Usable from:

  • Any class in the same assembly.

  • Derived classes from other assemblies.

private protected (C# 7.2+)

  • Accessible in:

    • Same class
      Derived classes in the same assembly

  • Not accessible in derived classes from a different assembly.

private protected int internalId;


Combined example: 

public class Person

{
    private string ssn;
    protected string nationality;
    internal string email;
    protected internal string address;
    private protected string bankAccount;

    public string Name { get; set; }

    public void SetSSN(string val) => ssn = val;
}


ssn: Only within Person

nationality: Accessible in Person and its subclasses

email: Accessible in any class in the same project

address: Accessible in subclasses or same project

bankAccount: Accessible only in subclasses in same project

6. Interfaces And Abstract classes

They're both used for abstraction, but they serve slightly different purposes.

Interfaces

An interface is a contract that only contains method signatures (no implementation). A class or struct that implements the interface must provide implementations for all of its members.


public interface IShape
{
    double GetArea();
}


Implementing an interface

public class Circle : IShape
{
    public double Radius { get; set; }

    public double GetArea() => Math.PI * Radius * Radius;
}


 Key Points:

  • No fields or constructors allowed

  • Can have default implementations (C# 8+)

  • A class can implement multiple interfaces

  • Used for polymorphism and loose coupling

Example of polymorphism

IShape shape = new Circle { Radius = 5 };
Console.WriteLine(shape.GetArea());


Abstract Classes

An abstract class is a partially implemented class that can have both abstract (unimplemented) and non-abstract (implemented) members.

You cannot instantiate an abstract class directly.


Syntax

public abstract class Animal
{
    public abstract void Speak();  // No body
    public void Eat() => Console.WriteLine("Eating...");
}


Derived class:

public class Dog : Animal
{
    public override void Speak() => Console.WriteLine("Bark!");
}


Key Points:

  • Can include fields, constructors, methods, properties

  • Use when you want to provide shared base functionality

  • Only one abstract base class per class (single inheritance)


Feature

Interface

Abstract Class

Contains implementation

❌ (except default methods in C# 8+)

✅ (can contain full/partial)

Fields

Constructors

Inheritance

Multiple interfaces

Only one base class

Use case

Capability/contract

Base type with common logic

Access modifiers

Not allowed (all public)

Allowed (public, protected, etc.)


Interface + Abstract Class Together

public interface ILogger
{
    void Log(string message);
}

public abstract class LoggerBase : ILogger
{
    public abstract void Log(string message);

    public void LogDate() => Console.WriteLine(DateTime.Now);
}

 


When to Use What?

Scenario

Use...

Define shared behavior with common code

Abstract Class

Enforce a contract without shared logic

Interface

Need multiple inheritance (C# doesn’t allow class-based MI)

Interface

Plugin or extensibility system

Interface


Real-World Analogy

  • Interface: Like a remote control interface — different brands implement it (TV, AC) but the interface defines what buttons exist.

  • Abstract Class: Like a base machine blueprint — defines what all machines have in common and allows each machine to fine-tune.

7. Arrays and Collections

Arrays

An array in C# is a fixed-size, strongly typed data structure used to store a sequence of elements of the same type.


Examples:

int[] numbers = new int[3];       // Array of 3 integers
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;


string[] fruits = { "Apple", "Banana", "Orange" };


Collections

Collections provide dynamic storage — unlike arrays, they can grow or shrink in size and offer more functionality like searching, sorting, and filtering.



Namespace

Type

Description

System.Collections

Non-generic

Stores objects (object) — boxing/unboxing required

System.Collections.Generic

Generic Collections

Type-safe, faster, preferred in modern C#

System.Collections.Concurrent

Thread-safe collections

For multi-threaded apps


Generic Collections (Recommended)

Found in System.Collections.Generic

List<T> – Dynamic array

List<string> fruits = new List<string> { "Apple", "Banana" };
fruits.Add("Orange");
Console.WriteLine(fruits[1]); // Banana

Dictionary<TKey, TValue> – Key-value store

Dictionary<string, int> scores = new Dictionary<string, int>();
scores["Alice"] = 90;
Console.WriteLine(scores["Alice"]); // 90


HashSet<T> – Unique unordered values

HashSet<int> numbers = new HashSet<int> { 1, 2, 3, 2 };
Console.WriteLine(numbers.Count); // 3


Queue<T> – FIFO (First In First Out)

Queue<string> tasks = new Queue<string>();
tasks.Enqueue("Task1");
Console.WriteLine(tasks.Dequeue()); // Task1


Stack<T> – LIFO (Last In First Out)

Stack<int> stack = new Stack<int>();
stack.Push(10);
Console.WriteLine(stack.Pop()); // 10




Generic collection Interfaces 


IEnumerable<T>
  ├── ICollection<T>
  │     ├── IList<T>
  │     │     ├── List<T>
  │     │     └── BindingList<T>
  │     ├── ISet<T>
  │     │     ├── HashSet<T>
  │     │     └── SortedSet<T>
  │     └── IDictionary<TKey, TValue>
  │           ├── Dictionary<TKey, TValue>
  │           └── SortedDictionary<TKey, TValue>
  │           └── SortedList<TKey, TValue>
  │
  └── IReadOnlyCollection<T>
        ├── IReadOnlyList<T>
        │     └── ReadOnlyCollection<T>
        └── IReadOnlyDictionary<TKey, TValue>
              └── ReadOnlyDictionary<TKey, TValue>


Use cases and implementation

Interface

Use Case

Implemented By

IEnumerable<T>

Looping

All collections

ICollection<T>

Add/Remove/Count

List, HashSet

IList<T>

Index-based list

List, BindingList

IDictionary<TKey, TValue>

Key-value access

Dictionary, SortedDictionary

ISet<T>

Unique values

HashSet

IReadOnlyList<T>

Immutable list

List via .AsReadOnly()


IEnumerable<T>

  • Enables foreach loops.

  • Base for LINQ queries.

IEnumerable<int> nums = new List<int> { 1, 2, 3 };

IEnumerator<T>

  • Used internally by IEnumerable<T> to move through the collection.

  • Has methods like .MoveNext(), .Reset(), .Current.


ICollection<T>

  • Adds collection-wide operations:

    • Count, Add, Remove, Clear, Contains

ICollection<string> items = new List<string>();
items.Add("Apple");


IList<T>

  • Allows indexed access ([index]) and insertion/removal at specific positions.

IList<int> list = new List<int> { 1, 2, 3 };
Console.WriteLine(list[1]); // 2


IDictionary<TKey, TValue>

  • Key-value pairs.

  • Has methods like Add(key, value), ContainsKey, TryGetValue.

IDictionary<string, int> scores = new Dictionary<string, int>();
scores["Math"] = 95;


ISet<T>

  • Ensures uniqueness of elements.

  • No duplicates allowed.

ISet<int> ids = new HashSet<int> { 1, 2, 3, 3 };
Console.WriteLine(ids.Count); // 3 (no duplicate)

IReadOnlyCollection<T>, IReadOnlyList<T>, IReadOnlyDictionary<TKey, TValue>

  • Immutable views of collections.

  • Safe to expose in APIs to prevent modification.

IReadOnlyList<string> colors = new List<string> { "Red", "Green" };

IComparer<T> / IEqualityComparer<T>

  • Custom sorting or equality logic.

Example: Sorting people by age

class AgeComparer : IComparer<Person>
{
    public int Compare(Person x, Person y) => x.Age.CompareTo(y.Age);
}


Example: Custom equality for HashSet

class EmailComparer : IEqualityComparer<User>
{
    public bool Equals(User x, User y) => x.Email == y.Email;
    public int GetHashCode(User obj) => obj.Email.GetHashCode();
}


What is IQueryable<T>? 

It’s an interface from System.Linq.


public interface IQueryable<T> : IEnumerable<T>
{
    Expression Expression { get; }
    IQueryProvider Provider { get; }
}


 What It Means:

IQueryable<T> is used mostly with remote data sources (like databases).

It supports deferred execution and translates LINQ queries into SQL or other expressions (e.g., EF Core, LINQ to SQL).


IQueryable<Employee> query = dbContext.Employees.Where(e => e.Salary > 50000);


 Query is not executed until you enumerate it (e.g., ToList() or foreach).




Feature

IEnumerable<T>

IQueryable<T>

Collection (e.g. List<T>)

Stores data

❌ No

❌ No

✅ Yes

Can iterate

✅ Yes

✅ Yes (inherits IEnumerable)

✅ Yes

LINQ support

✅ Yes (in-memory)

✅ Yes (translated to queries)

✅ Yes

Deferred execution

✅ Yes

✅ Yes

❌ (unless LINQ used)

Designed for

In-memory data

Remote/queryable data

In-memory data


Real-World Analogy

  • List<T> is like a bookshelf — it stores books (data).

  • IEnumerable<T> is like saying, "I can walk along this shelf and look at each book."

  • IQueryable<T> is like saying, "Tell the librarian to fetch books where author = 'Faizal'."

Non-Generic Collections (Older, less used)


Stored as object, so boxing/unboxing required:

  • ArrayList: Like List<object>

  • Hashtable: Like Dictionary<object, object>

  • Queue, Stack (non-generic versions)


🔍 Avoid unless maintaining legacy code.

LINQ with Collections

C# supports LINQ queries on collections:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evens = numbers.Where(n => n % 2 == 0).ToList();



Interfaces: 


IEnumerable
  ├── ICollection
  │     ├── IList
  │     │     ├── ArrayList
  │     │     └── SortedList
  │     └── IDictionary
  │           ├── Hashtable
  │           └── SortedList (non-generic)


Concurrent Collections (for multithreading)

  • ConcurrentDictionary<TKey, TValue>

  • ConcurrentQueue<T>

  • ConcurrentBag<T>

Used when multiple threads need to access the collection safely.

Choosing the Right Collection

Collection

Best for

List<T>

Ordered list, fast read/write by index

Dictionary<K,V>

Lookup by key

HashSet<T>

Unordered unique items

Queue<T>

FIFO processing

Stack<T>

LIFO processing

Concurrent...

Safe multithreaded access

 

Interfaces 


IEnumerable<T>
  └── IProducerConsumerCollection<T>
          ├── ConcurrentQueue<T>
          ├── ConcurrentStack<T>
          ├── ConcurrentBag<T>
          └── BlockingCollection<T>

IDictionary<TKey, TValue>
  └── ConcurrentDictionary<TKey, TValue>




 Example Combining Them

Dictionary<string, List<string>> students = new();

students["Grade1"] = new List<string> { "Asha", "Ravi" };
students["Grade2"] = new List<string> { "Leela" };

foreach (var grade in students)
{
    Console.WriteLine($"{grade.Key}: {string.Join(", ", grade.Value)}");
}


Custom Collection Class (Advanced)

You can also create your own collection class by implementing:


  • IEnumerable<T> (for iteration)

  • ICollection<T> (for add/remove/count)

  • IDictionary<K,V> (for key-value stores)


Array vs Collection

 Are Arrays Collections?

Not exactly — but close.

  • Arrays are not part of the System.Collections namespace, so technically they're not "collections" in the same way as List<T> or Dictionary<K,V>.

  • However, arrays do implement:

    • IEnumerable (non-generic)

    • IEnumerable<T> (generic)

    • So they can be used with LINQ and foreach loops, just like collections.

Arrays behave like collections, but they are not part of the .NET Collections Framework.


Array vs Collection Comparison

Feature

Arrays

Collections (e.g., List)

Size

Fixed

Dynamic (can grow/shrink)

Type Safety

Strongly typed

Strongly typed (Generic)

LINQ Compatible

✅ Yes

✅ Yes

Resize Support

❌ No

✅ Yes

Performance

Slightly faster

Slightly slower (more features)

Use case

When size is known

When size is unknown or changes


Example: Using Array in a foreach Loop

int[] scores = { 80, 90, 100 };

foreach (int score in scores)
{
    Console.WriteLine(score);
}


When to Use Arrays vs Collections


Use Case

Use Array

Use Collection

Fixed number of elements

Dynamic size, insert/delete needed

Frequent resizing or sorting

Interfacing with legacy .NET APIs


Pro Tip

In most real-world C# applications:

  • Use arrays when performance matters and size is fixed.

  • Use List<T> when working with dynamic data

8. Delegates, Events, and Lambdas

Delegates

A delegate is a type-safe function pointer — it can hold a reference to a method with a specific signature.


public delegate int Calculator(int x, int y);  // defines a delegate type

public int Add(int a, int b) => a + b;

Calculator calc = Add;  // assign method to delegate
int result = calc(2, 3);  // result = 5


Use Cases:

  • Callback methods

  • Event handling

  • Plug-in patterns

Multicast Delegates

Delegates can hold multiple methods — they’re invoked in order.

public delegate void Notify();

Notify n = Method1;
n += Method2;
n();  // Both Method1 and Method2 will be called


Events

An event is a wrapper over delegates. It provides a publisher-subscriber model, commonly used in UI or real-time systems.

public delegate void AlarmHandler();

public class Alarm
{
    public event AlarmHandler OnRing;

    public void Ring()
    {
        OnRing?.Invoke();  // safe call
    }
}


Subscribing to Events:

Alarm a = new Alarm();
a.OnRing += () => Console.WriteLine("Alarm rang!");
a.Ring();  // Output: Alarm rang!


Lambda Expressions

A lambda is an anonymous method (a function without a name) written using the => syntax.


(x, y) => x + y


Example:

Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4));  // 7


Built-in Delegates

To avoid defining custom delegate types every time, C# provides these predefined generic delegates:

Func<T>

  • Returns a value

  • Can take 0 to 16 parameters

  • Last generic type is the return type

Func<int, int, int> multiply = (a, b) => a * b;
int result = multiply(3, 4);  // 12


Action<T>

Returns void

Can take 0 to 16 parameters


Action<string> greet = name => Console.WriteLine($"Hello, {name}!");
greet("Faizal");  // Output: Hello, Faizal!


Predicate<T>

Returns a bool

Takes exactly 1 parameter


Predicate<int> isEven = num => num % 2 == 0;
Console.WriteLine(isEven(4));  // True


Comparison Table

Type

Input(s)

Return Type

Use Case

Delegate

Custom

Custom

General-purpose, reusable

Event

Delegate-based

N/A

Publisher-subscriber

Func<T,...>

0–16 params

T (last)

Returns a value

Action<T,...>

0–16 params

void

No return value

Predicate<T>

1 param

bool

Test a condition (true/false)

Lambda

Anonymous

Any

Inline functions, concise code


Real-World Use Example:

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

Predicate<int> isOdd = x => x % 2 != 0;
List<int> oddNumbers = numbers.FindAll(isOdd);


Bonus: Event with Built-in Delegate

public class Button
{
    public event Action OnClick;

    public void Click()
    {
        OnClick?.Invoke();
    }
}

9. LINQ (Language Integrated Query)

What is LINQ?

LINQ (Language Integrated Query) allows you to query collections (arrays, lists, XML, databases, etc.) using a SQL-like syntax — but in C#.

It integrates query capabilities directly into C# through extension methods and lambda expressions.

Where You Can Use LINQ

  • Arrays

  • List<T>, Dictionary<K,V>, HashSet<T>, etc.

  • XML (System.Xml.Linq)

  • SQL databases (via LINQ to Entities / LINQ to SQL)

  • JSON or in-memory objects

Two LINQ Syntaxes

 1. Query Syntax (SQL-like):

var result = from n in numbers
            where n % 2 == 0
            select n;

2. Method Syntax (Fluent, Lambda-based):

var result = numbers.Where(n => n % 2 == 0);

Both are functionally equivalent — method syntax is more common in real-world C# apps.

Example with List<int>

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();

Common LINQ Methods (with Lambdas)

Method

Purpose

Example

Where()

Filter based on condition

x => x > 10

Select()

Projection (transform values)

x => x * x

OrderBy()

Sort ascending

x => x.Name

OrderByDescending()

Sort descending

x => x.Score

GroupBy()

Group by key

x => x.Category

ToList()

Convert to list

.ToList()

First(), FirstOrDefault()

Get first match

.FirstOrDefault(x => x > 0)

Any()

Returns true if any item matches

.Any(x => x == 5)

All()

True if all match

.All(x => x > 0)

Count()

Count elements

.Count()

Sum()

Sum numeric values

.Sum(x => x.Price)

Distinct()

Removes duplicates

.Distinct()

Join()

Combine two sequences

Useful in LINQ to SQL

Real-World Example: Working with Objects

class Employee
{
    public string Name;
    public string Department;
    public int Salary;
}

List<Employee> employees = new()
{
    new Employee { Name = "Asha", Department = "HR", Salary = 50000 },
    new Employee { Name = "Ravi", Department = "IT", Salary = 70000 },
    new Employee { Name = "Leela", Department = "IT", Salary = 80000 }
};

// Employees in IT dept earning > 60000
var result = employees
    .Where(e => e.Department == "IT" && e.Salary > 60000)
    .Select(e => e.Name);

foreach (var name in result)
    Console.WriteLine(name);  // Outputs: Ravi, Leela

LINQ Behind the Scenes

LINQ uses deferred execution:

  • The query isn't executed until you iterate or call ToList(), First(), etc.

  • Helps with performance when working with large datasets or databases.

LINQ in Databases: LINQ to Entities

If you're using Entity Framework, you can write:

var users = dbContext.Users
                    .Where(u => u.Age > 18)
                    .OrderBy(u => u.Name)
                    .ToList();


EF will translate it into SQL behind the scenes!


Benefits of LINQ

  • Clean and readable code

  • Type safety (compile-time checks)

  • IntelliSense support in IDE

  • Works across data types (objects, XML, SQL, JSON)

Summary

Feature

LINQ Supports

Filtering

Where, First, etc.

Sorting

OrderBy, ThenBy

Projection

Select

Grouping

GroupBy

Aggregation

Count, Sum, Max

Set operations

Distinct, Union

Joins

Join, GroupJoin




10. Exception Handling

Exception handling is a mechanism to catch and handle runtime errors in a structured and safe way, without crashing the application.

C# provides a robust exception handling model using:

try - catch - finally - throw


Why Exception Handling?

  • Prevents program crashes due to unexpected situations.

  • Helps maintain application stability and user experience.

  • Enables logging and error tracking.

  • Keeps business logic separate from error-handling logic.

Basic Syntax

try
{
    // Code that might throw exception
    int x = 10;
    int y = 0;
    int result = x / y; // Will throw DivideByZeroException
}
catch (DivideByZeroException ex)
{
    Console.WriteLine("Cannot divide by zero.");
}
catch (Exception ex)
{
    Console.WriteLine("General error: " + ex.Message);
}
finally
{
    Console.WriteLine("This block always runs.");
}

Keywords Explained

Keyword

Purpose

try

Wraps code that might throw exceptions.

catch

Handles exceptions that occur in try block.

finally

Executes code whether an exception is thrown or not. Useful for cleanup (closing files, connections, etc.).

throw

Manually throws an exception.

Example: Throwing Custom Exceptions

public void Withdraw(decimal amount)
{
    if (amount > balance)
        throw new InvalidOperationException("Insufficient funds.");
}

Common Exceptions in C#

Exception Type

Scenario

NullReferenceException

Accessing a null object

DivideByZeroException

Division by zero

FileNotFoundException

File not found on disk

IndexOutOfRangeException

Invalid array index

InvalidOperationException

Invalid method call or object state

Custom Exception Class (Advanced)

public class InvalidUserInputException : Exception
{
    public InvalidUserInputException(string message) : base(message) { }
}

Use it like:

throw new InvalidUserInputException("Username cannot be empty.");

Best Practices

  • ✅ Catch specific exceptions first

  • ✅ Avoid empty catch blocks

  • ✅ Use finally for cleanup

  • ✅ Use custom exceptions for domain-specific errors

  • ✅ Don't catch exceptions you can't handle

  • ✅ Log errors for diagnostics

When to Use try-catch

Scenario

Use try-catch?

Reading files or databases

✅ Yes

Parsing user input

✅ Yes

Logic errors (should be avoided)

❌ No – fix logic

Validation errors

❌ Use if-else or guards

11. Garbage Collection & Memory Management

Garbage Collection (GC) in C# is an automatic memory management system provided by the .NET runtime.

It:

  • Reclaims memory used by objects that are no longer accessible.

  • Frees developers from manual memory allocation and deallocation (like in C/C++).

  • Helps prevent memory leaks and dangling pointers.

Key Concepts

Term

Meaning

Heap

Area in memory where reference types (objects, arrays, strings) are stored.

Stack

Stores value types and method calls.

Managed Memory

Memory managed by the CLR (Common Language Runtime).

Unmanaged Memory

Memory not handled by CLR (e.g., file handles, DB connections, native code).

How Garbage Collector Works

Generation

Description

Gen 0

Short-lived objects (local vars, temp objects)

Gen 1

Buffer between Gen 0 and Gen 2

Gen 2

Long-lived objects (static objects, global caches)

Process:

  1. GC marks objects that are still in use.

  2. It sweeps and removes unused objects.

  3. It compacts memory by shifting live objects together.

This improves memory efficiency and performance.

 When does GC run?

When system memory is low

When Gen 0 or Gen 1 thresholds are hit

When you call GC.Collect() (not recommended unless absolutely necessary)


 IDisposable and using Statement

For objects using unmanaged resources (like files, database connections), C# uses:


IDisposable interface:

class FileLogger : IDisposable
{
    FileStream file;

    public FileLogger(string path)
    {
        file = new FileStream(path, FileMode.Create);
    }

    public void Dispose()
    {
        file.Close();  // release unmanaged resource
    }
}


using block:

using (FileLogger logger = new FileLogger("log.txt"))
{
    // use logger
// Dispose() is called automatically


Weak References

Allows the GC to collect the object even if you still have a reference.

WeakReference objRef = new WeakReference(new MyClass());


Useful for caching scenarios.

Finalizers (Destructors)

If you can't use IDisposable, define a finalizer:

~MyClass()
{
    // Cleanup code
}

⚠ Finalizers slow down GC and should be avoided unless absolutely needed.


Best Practices

✅ Use using for IDisposable objects

✅ Avoid unnecessary object creation

✅ Don’t call GC.Collect() unless required

✅ Prefer composition over inheritance for large objects

✅ Be cautious with static references (they survive until app exits)

Summary

Concept

Role

Garbage Collector

Reclaims unused memory

Generations

Optimizes GC performance

IDisposable

Releases unmanaged resources

using

Auto-disposes resources

Finalizer

Last resort cleanup












Comments