Showing posts with label CSharp. Show all posts
Showing posts with label CSharp. Show all posts

Wednesday, July 1, 2026

C# generic method with or without method parameter

In C#, a generic method is a method that is declared with a type parameter (usually denoted as <T>). However, when the method is called then type argument for type parameter is optional or not, is explained in this post.

Here is a breakdown of generic methods with and without method parameters, followed by the core differences.

Generic Method WITH Method Parameters

In this scenario, the type parameter <T> is used as the data type for one or more of the arguments passed into the method.

Example

public class Utility
{
    // The type parameter T is used in
    // the method parameter (T input)
    public void PrintDetails<T>(T input)
    {
        Console.WriteLine($"Type: " +
            $"{typeof(T)}, Value: {input}");
    }
}

How you call it:

Because the compiler can see what you are passing into the method, it can automatically figure out what T is. This is called Type Inference.

class Program
{
    static void Main(string[] args)
    {
        Utility util = new Utility();

        // 1. Calling WITHOUT explicitly
        // specifying the type (Type Inference)
        util.PrintDetails("Hello World"); // Compiler infers T is string
        util.PrintDetails(123); // Compiler infers T is int

        // 2. Calling WITH explicitly specifying the type
        util.PrintDetails<double>(45.67);
    }
}

Generic Method WITHOUT Method Parameters

In this scenario, the type parameter <T> is used inside the method body or as the return type, but it does not appear in the method's input parameters.

Example

public class Factory
{
    // T is the return type, but 
    // the method takes no arguments
    public T CreateInstance<T>() where T : new()
    {
        return new T();
    }
}

How you call it:

Because there are no arguments being passed, the compiler has zero clues to figure out what T should be. Therefore, you must explicitly provide the type parameter.

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        Factory factory = new Factory();

        // You MUST explicitly state <Person> or <List<int>>
        Person p = factory.CreateInstance<Person>();
        var list = factory.CreateInstance<List<int>>();

        // THIS WILL FAIL TO COMPILE:
        // Error: Type arguments cannot be inferred
        // var item = factory.CreateInstance(); 

    }
}

Key Differences
Feature With Method Parameter (Method<T>(T arg)) Without Method Parameter (Method<T>())
Type Inference Supported. The compiler looks at the argument passed to determine T. Not Supported. The compiler has no input data to analyze.
Calling Syntax Clean and implicit: Method(argument) Explicit: Method<Type>()
Primary Use Case Processing, comparing, or operating on data of a flexible type (e.g., swapping two values, logging an object). Creating instances, fetching configuration, or casting data (e.g., Dependency Injection containers, ORMs like Entity Framework).

Summary

  • Use with method parameters when the method needs to ingest and act upon generic data. You get the benefit of cleaner code via type inference.
  • Use without method parameters when the method needs to generate, fetch, or convert something into a specific type from scratch. You must explicitly tell the compiler what type you want.

C# generic ObservableCollection Best Practices

In this post, you see how to work with ObservableCollection<T> in C#.

In C#, an ObservableCollection<T> is a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. It is the absolute go-to collection in data-binding scenarios (like WPF, MAUI, WinUI, and Blazor) because it automatically keeps the UI in sync with your underlying data.

Here is a complete guide on hoftw to work with it.

Why Use ObservableCollection<T>?

If you use a standard List<T> and bind it to a UI element (like a ListView), adding an item to the list won't change what you see on the screen. The UI has no way of knowing the list changed.

ObservableCollection<T> implements the INotifyCollectionChanged interface. Whenever the collection is modified, it fires an event that tells the UI, "Hey, I just changed! Redraw yourself."

Basic Setup and Usage

To use it, you need to import the System.Collections.ObjectModel namespace.

using System.Collections.ObjectModel;
using System.Collections.Specialized;
class Program
{
    static void Main()
    {
        // 1. Initialization
        ObservableCollection<string> superheroes = new ObservableCollection<string>()
        {
            "Batman",
            "Superman"
        };
        // 2. Subscribe to changes (Optional: Usually the UI does this automatically)
        superheroes.CollectionChanged += Superheroes_CollectionChanged;
        // 3. Modifying the collection triggers the event
        superheroes.Add("Wonder Woman");
        superheroes.Remove("Superman");
    }
    // Event handler that fires every time the collection changes
    private static void Superheroes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        Console.WriteLine($"Collection changed! Action: {e.Action}");
        if (e.NewItems != null)
        {
            foreach (var item in e.NewItems)
                Console.WriteLine($"Added: {item}");
        }
        if (e.OldItems != null)
        {
            foreach (var item in e.OldItems)
                Console.WriteLine($"Removed: {item}");
        }
    }
} 
Notes
  1. In many ways, working with ObservableCollection<T> is identical to working with List<T>, given that both of these classes implement the same core interfaces. What makes the ObservableCollection<T> class unique is that this class supports an event named CollectionChanged. This event will fire whenever a new item is inserted, a current item is removed (or relocated), or the entire collection is modified.
  2. CollectionChanged event is defined in terms of a delegate, NotifyCollectionChangedEventHandler. This delegate can call any method that takes an object as the first parameter and takes a NotifyCollectionChangedEventArgs as the second.
  3. The NotifyCollectionChangedEventArgs parameter defines two important properties, OldItems and NewItems, which give you a list of items that were currently in the collection before the event fired and the new items that were involved in the change.
  4. The NotifyCollectionChangedEventArgs parameter defines Action property, which returns enum type NotifyCollectionChangedAction value (Add = 0, Remove = 1, Replace = 2, Move = 3, Reset = 4).

Crucial Gotchas & Best Practices

While ObservableCollection<T> is incredibly useful, it has a few quirks that can trip you up if you aren't careful.

⚠️ Gotcha 1: It only tracks Collection changes, not Property changes

If you modify a property of an item inside the collection, the collection does not fire a notification.

  • Triggers UI Update: Add(new User { Name = "Alice" });
  • Does NOT Trigger UI Update: myCollection[0].Name = "Bob";

The Fix: The objects inside your collection must implement the INotifyPropertyChanged interface.

⚠️ Gotcha 2: UI Thread Limitations

By default, if you try to modify an ObservableCollection<T> from a background thread while it is data-bound to a UI element, your app will crash with a cross-thread exception.

The Fix: In WPF, you can use BindingOperations.EnableCollectionSynchronization to allow multi-threaded access, or ensure you marshal modifications back to the main thread using something like MainThread.BeginInvokeOnMainThread (in MAUI) or the UI dispatcher.

⚠️ Gotcha 3: Performance with Bulk Updates

If you add 1,000 items to an ObservableCollection<T> using a foreach loop, it will fire the CollectionChanged event 1,000 times. This can cause severe UI stuttering.

The Fix: Create a custom class that inherits from ObservableCollection<T> and implements an AddRange method that suppresses notifications until all items are added:

public class RangeObservableCollection<T> : ObservableCollection<T>
{
    public void AddRange(IEnumerable<T> list)
    {
        // Suppress notification logic or temporarily detach event,
        // add items, and fire a single Reset action notification.
        foreach (var item in list)
        {
            this.Items.Add(item);
        }
        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
}

Summary Comparison

Feature List<T> ObservableCollection<T>
Best Used For Backend logic, data processing, loops UI Data Binding
Performance Faster (less overhead) Slower (fires events on changes)
Notifies UI on Add/Remove? No Yes
Notifies UI on Internal Property Change? No No (Requires INotifyPropertyChanged on the T item)

C# NameValueCollection comapred with generic Dictionary

In this post, we wiil see:
  • How is NameValueCollection different from Dictionary? 
  • When to use NameValueCollection?
First look at the following example of NameValueCollection.
Example
using System.Collections.Specialized;
public class Program
{
    public static void Main()
    {
        // Creates and initializes a new NameValueCollection.
        NameValueCollection myCol = new NameValueCollection();
        myCol.Add("red", "Rose");
        myCol.Add("green", "Flag");
        myCol.Add("blue", "Sky");
        myCol.Add("blue", "Sky Blue");
        myCol.Add("blue", "Navy Blue");
        myCol.Add(null, "Null key");

        Console.WriteLine("Count: {0}", myCol.Count);
        foreach (string? k in myCol.AllKeys)
        {
            Console.WriteLine("Key:{0} Value:{1}", k, myCol[k]);
        }
        Console.ReadKey();
    }

}
/*
OUTPUT
Count: 3
Key: red Value:Rose
Key:green Value:Flag
Key:blue Value:Sky, Sky Blue, Navy Blue
Key: Value:Null key
*/

The above example code actually perfectly demonstrates the biggest fundamental difference between the two!

Notice the "blue" key is added three times? Instead of throwing an error or overwriting the old value, it combined them into a single, comma-separated string ("Sky, Sky Blue, Navy Blue"). A standard Dictionary would never let you get away with that.

Here is a breakdown of how NameValueCollection differs from Dictionary<TKey, TValue> and exactly when you should use it.

Key Differences

Feature NameValueCollection Dictionary<string, string>
Duplicate Keys Allowed. Automatically joins multiple values under the same key with a comma. Not Allowed. Will throw an exception on .Add() or overwrite the value if using the indexer dict[key] = value.
Type Safety Strictly string only for both keys and values. Generic. Can use any types (e.g., Dictionary<int, Customer>).
Access Methods Can look up values by Key (string) OR by Index (integer). Can only look up values by Key.
Performance Slower. It is an older, non-generic collection (legacy System.Collections.Specialized). Faster. Highly optimized for hash-lookups and avoids boxing/unboxing.
Null Keys Allowed. You can have a null key holding values. Not Allowed. Will throw an ArgumentNullException.

Internal Structure Contrast

Under the hood, NameValueCollection handles duplicate keys by managing an array of strings for each key, which it flattens when you use the default string indexer.

NameValueCollection Structure:

[ "red" ]   ---> [ "Rose" ]

[ "green" ] ---> [ "Flag" ]

[ "blue" ]  ---> [ "Sky", "Sky Blue", "Navy Blue" ]  <-- (Flattens to "Sky,Sky Blue,Navy Blue")

When to use NameValueCollection?

In modern .NET development, Dictionary<TKey, TValue> (or ILookup<TKey, TValue>) is the default choice. However, NameValueCollection is still the right tool for specific scenarios:

1. Handling HTTP Query Strings and Headers

The web inherently allows duplicate keys in URLs (e.g., ?tag=csharp&tag=dotnet&tag=collections). NameValueCollection is uniquely suited for this, which is why legacy ASP.NET used it heavily for Request.QueryString and Request.Headers.

2.Legacy .NET Configuration Files

Older .config files (app.config / web.config) store <appSettings> as a NameValueCollection. If you are dealing with legacy configuration APIs, you'll still run into it.

3. When you need both Key and Ordered Index access

If you need to fetch elements both by their string identifier and by their numerical insertion order (myCol[0]), NameValueCollection supports this out of the box.

Modern Alternatives to Keep in Mind

If you need to map one key to multiple values in modern C# without using an old, string-only collection, consider:

  • Dictionary<string, List<string>>: Gives you strict generic type safety, but you have to write the boilerplate code to initialize the inner list for new keys.
  • ILookup<TKey, TElement>: Created via LINQ (.ToLookup()). Great for multi-value groups, but it is immutable once created.

 

Tuesday, June 30, 2026

C# Dictionary Initialization approaches

1. The most common approach is collection initializer.
// collection initializer syntax
// each key-value appear to be elements of set
Dictionary<int, string> sets = new Dictionary<int, string>
{
{101,"Red" },
{102,"Green" },
{103,"Blue" },
{104,"Cyan" },
{105,"Orange" },
};
2. The second approach is to use target type with new().
// Target type syntax
Dictionary<int, string> sets2 = new()
{
{101,"Red" },
{102,"Green" },
{103,"Blue" },
{104,"Cyan" },
{105,"Orange" },
};
3. The third approach is to use var with new() constructor
// var syntax
var sets3 = new Dictionary<int, string>()
{
{101,"Red" },
{102,"Green" },
{103,"Blue" },
{104,"Cyan" },
{105,"Orange" },
};
4. The fourth approach is to use index style where index is key
// index style syntax
Dictionary<int, string> indices1 = new Dictionary<int, string>
{
[101] = "Red",
[102] = "Green",
[103] = "Blue",
[104] = "Cyan",
[105] = "Orang",
};
5. The fifth approach is to use index style with collection expression
// index style syntax, collection expression
Dictionary<int, string> indices2 = new()
{
[101] = "Red",
[102] = "Green",
[103] = "Blue",
[104] = "Cyan",
[105] = "Orang",
};
6. The sixth approach is to use index style with var keyword
// index style syntax, collection expression
var indices3 = new Dictionary<int, string>()
{
[101] = "Red",
[102] = "Green",
[103] = "Blue",
[104] = "Cyan",
[105] = "Orang",
};
}
}

C# List is generally preferred over StringCollection. Why

In modern C# development, List<string> is almost universally preferred over StringCollection. The short answer is that StringCollection is a relic of the past, while List<T> represents the modern, type-safe standard.

Here is a breakdown of exactly why List<string> wins across the board:
1. Generics vs. Non-Generics (The History Lesson)
  • StringCollection belongs to the System.Collections.Specialized namespace and was introduced in .NET 1.1—before Generics existed. It internally stores data as an array of object types.
  • List<T> was introduced in .NET 2.0 with the System.Collections.Generic namespace.
  • Because List<string> uses generics, the compiler knows exactly what type of data is inside the list at compile time.
2. Performance and Memory Efficiency
  • Because StringCollection handles data as object types behind the scenes, it can incur performance penalties. While strings are reference types (meaning they don't suffer from traditional "boxing" like integers do), StringCollection still requires frequent casting from object back to string when you retrieve items.
  • List<string> avoids all casting overhead, operating directly on the string type, making it faster and lighter on memory.
3. LINQ Support
  • List<string> implements IEnumerable<string>, which means it works seamlessly out-of-the-box with all LINQ methods (.Where(), .Select(), .OrderBy(), etc.).
  • StringCollection only implements the non-generic IEnumerable. If you want to use LINQ with it, you are forced to call .Cast<string>() first, adding unnecessary boilerplate code:
// Modern and clean with List<string>
var result = myList.Where(s => s.StartsWith("A")).ToList();

// Clunky with StringCollection
var result = myStringCollection.Cast<string>().Where(s => s.StartsWith("A")).ToList();

4. Richer API and Ecosystem Fit
  • List<T> comes with a massive array of built-in utility methods that StringCollection lacks, such as .AddRange(), .RemoveAll(), .Find(), .TrueForAll(), and .Sort().
  • Furthermore, almost every modern third-party library, NuGet package, and internal .NET API expects generic collections (List<T>, IEnumerable<T>, IList<T>). Using StringCollection forces you to constantly convert your data back and forth just to interact with modern APIs.
Summary Comparison
Feature List<string> StringCollection
Introduced .NET 2.0 (Modern standard) .NET 1.1 (Legacy/Obsolete)
Type Safety Compile-time guaranteed Run-time checked
LINQ Support Native Requires .Cast<string>()
Performance Optimized Slower due to casting
Example of StringCollection
using System.Collections.Specialized;
class Program
{
    static void Main(string[] args)
    {
        StringCollection words = new StringCollection();
        words.Add("Hello");
        words.Add("World");
        words.AddRange(new string[] { "Ajeet", "Kumar", "Gupta" });
        if (words.Contains("Kumar"))
        {
            Console.WriteLine("Total words:" + words.Count);//5
        }
        words.Clear();
        Console.WriteLine("Total words:" + words.Count);//0
        words.Insert(0, "Hello");
        words.Insert(1, "Kumar");
        words.Insert(2, "Ajeet");
        words.Insert(3, "Gupta");
        words.Insert(3, "Gaytri");
        Console.WriteLine("Total words:" + words.Count);//5
        words.Remove(words[1]);
        Console.WriteLine("Total words:" + words.Count);//4
        words.RemoveAt(3);
        Console.WriteLine("Total words:" + words.Count);//3

        // iteration using indexer
        for (int i = 0; i < words.Count; i++)
        {
            Console.Write(words[i] + " "); // Hello Ajeet Gaytri
        }
        Console.WriteLine();
        // iteration using StringEnumerator
        StringEnumerator enumerator = words.GetEnumerator();
        while (enumerator.MoveNext())
        {
            Console.Write(enumerator.Current + " ");
        }
        Console.WriteLine();
        // StringCollection implements IEnumerable
        foreach (var word in words)
        {
            Console.Write(word + " ");
        }
    }
}

C# How to access protected implemented method of interface

Since C#8.0, default implemented method is allowed. This allows for different access modifiers (e.g. public, private, protected) with default method. The question is- how to access protected method in implementing class?
interface ILogger
{
    protected void Print()
    {
        Console.WriteLine("Protected member");
    }
}

class MyLogger : ILogger
{
    public void GetProtected()
    {
        ILogger logger = new MyLogger();
    }
}

Answer: In C# (default interface methods, C# 8+), a protected method inside an interface is not accessible from the implementing class instance directly.

In above code, Print() is protected inside the interface, which means:

  • It is available only to derived interfaces, not implementing classes.
  • MyLogger cannot call Print() directly.
  • Even ILogger logger = new MyLogger(); does not expose Print().
  • So this will not compile:

If you want implementing classes to access helper logic, use protected inside a public method. So, indirectly protected method can be accessed via public method.

Example:

interface ILogger
{
    protected void Print()
    {
        Console.WriteLine("Protected member");
    }
    public void Log()
    {
        Print(); // allowed inside interface
    }
}

class MyLogger : ILogger
{
    public void GetProtected()
    {
        ILogger logger = new MyLogger();
        logger.Log();
    }
}
class Program
{
    static void Main(string[] args)
    {
        MyLogger logger = new MyLogger();
        logger.GetProtected();
    }
}
Note. The private and protected members in interfaces exists mainly for derived interfaces, not implementing classes. The private and protected members of interface are accessed indirectly via public member.

 

Sunday, June 28, 2026

C# How to use private method implemented in an interface type

We know that we cannot use private member outside its own type. The private member can be consumed only inside its own type. This is true for interface as well. Let us consider the following code. Since Print method is not consumed inside ICommon interface, it is useless. Then how to use a private method inside an interface? We can use private method as a helper method.
interface ICommon
{
    private void Print()
    {
        Console.WriteLine("C#8 onward access modifier allowed.");
    }
}

class Test : ICommon { }
class Program
{
    static void Main(string[] args)
    {
        Test test = new Test();
    }
}
Example
We can create private method inside an interface as a helper method. 
interface ILogger
{
    void LogInfo(string msg)
    {
        Write("INFO", msg);
    }

    void LogError(string msg)
    {
        Write("ERROR", msg);
    }

    // shared helper
    private void Write(string type, string msg)
    {
        Console.WriteLine($"[{type}] {msg}");
    }
}

class MyLogger : ILogger { }

class Program
{
    static void Main()
    {
        ILogger logger = new MyLogger();

        logger.LogInfo("Started");
        logger.LogError("Failed");
    }
}
OUTPUT
[INFO] Started
[ERROR] Failed

C# Comparison of IComparer with generic IComparer type

The generic IComparer<T> is type safe compared to nongeneric IComparer. A separate class is used to compare property or properties of type T. In the following example, names of employees are compared which helps to sort the employees by names.
class Employee
{
    public string? Name { get; set; }
    public int Age { get; set; }
}

// IComparer<Employee> is used to compare 2 Employee objects
// IComparer<string> is used to compare strings
// Since, you have to compare names of 2 employees
// hence, IComparer<Employee> is valid
class EmployeeNameComparer : IComparer<Employee>
{
    public int Compare(Employee? x, Employee? y)
    {
        if (x != null && y != null)
        {
            return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
        }
        else
        {
            return 0;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Employee[] employees = {
            new Employee { Name = "Rajesh", Age = 21 } ,
            new Employee { Name = "Ajay", Age = 23 },
            new Employee { Name="Niraj",Age=31}
        };
        Console.WriteLine("---Before Sorting---");
        foreach (var employee in employees)
        {
            Console.WriteLine($"Name: {employee.Name} Age:{employee.Age}");
        }
        Array.Sort<Employee>(employees, new EmployeeNameComparer());
        Console.WriteLine("---After Sorting---");
        foreach (var employee in employees)
        {
            Console.WriteLine($"Name: {employee.Name} Age:{employee.Age}");
        }
    }
}
OUTPUT
---Before Sorting---
Name: Rajesh Age:21
Name: Ajay Age:23
Name: Niraj Age:31
---After Sorting---
Name: Ajay Age:23
Name: Niraj Age:31
Name: Rajesh Age:21

The nongeneric IComparer example is given in another post. Click here.

The generic IComparer<in T> and Contravariance
Since the generic IComparer<T> supports contravariance, we can use more derived type also. For example, IComparer<Employee> can be substituted by IComparer<Manager> where Manager is derived class from Employee class.

// This compiles successfully ONLY because of contravariance (the 'in' keyword)
IComparer<Manager> managerComparer = new EmployeeNameComparer();

Since IComparer<in T> by definition supports contravariance. It means if I have IComparer<Employee> implemented by EmployeeNameComparer then IComparer<Manager> need not be again implemented by some other class. Same class EmployeeNameComparer can be used to sort managers name.
Example.
class Employee
{
    public string? Name { get; set; }
    public int Age { get; set; }
}
class Manager : Employee
{
    public int Salary { get; set; }
}

// IComparer<Employee> is used to compare 2 Employee objects
// IComparer<string> is used to compare strings
// Since, you have to compare names of 2 employees
// hence, IComparer<Employee> is valid
class EmployeeNameComparer : IComparer<Employee>
{
    public int Compare(Employee? x, Employee? y)
    {
        if (x != null && y != null)
        {
            return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
        }
        else
        {
            return 0;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Manager[] managers = {
            new Manager{ Name = "Rajesh", Age = 21 } ,
            new Manager{ Name = "Ajay", Age = 23 },
            new Manager{ Name="Niraj",Age=31}
        };
        Console.WriteLine("---Before Sorting---");
        foreach (var manager in managers)
        {
            Console.WriteLine($"Name: {manager.Name} Age:{manager.Age}");
        }
        Array.Sort<Employee>(managers, new EmployeeNameComparer());
        Console.WriteLine("---After Sorting---");
        foreach (var manager in managers)
        {
            Console.WriteLine($"Name: {manager.Name} Age:{manager.Age}");
        }
    }
}
Point to Note.
In C#, arrays of reference types are covariant. This means that a Manager[] can be implicitly cast to an Employee[]. When you call: Array.Sort(managers, new EmployeeNameComparer()); The C# compiler does not see a type mismatch because it implicitly cast Manager[] array into an Employee[]. It treats the array as a collection of employees, sorts them using Employee comparer, and because they are still Manager objects under the hood, it works flawlessly at runtime!

C# Using IEnumerator to loop throgh array

The foreach loop is shortcut for IEnumerator methods and properties. Since an array implements IEnumerable, we can use foreach loop.
class Program
{
    static void Main(string[] args)
    {
        int[] numbers = { 1, 2, 3, 4, 5 };
        foreach (var number in numbers)
        {
            Console.Write(number + " ");
        }
        
        Console.WriteLine("\n----Second Approach-----");
        // since every array implements IEnumerable
        // and IEnumerable contains GetEnumerator()
        // we get Enumerator object
        // numbers.GetEnumerator().Current should not be used in while loop
        // because it will create a new instance of enumerator object in each iteration
        
        var enumerator = numbers.GetEnumerator();// create a single instance
        
        while (enumerator.MoveNext())
        {
            Console.Write(enumerator.Current + " ");
        }
    }
}
1 2 3 4 5
----Second Approach-----
1 2 3 4 5

C# Example of Interface Hierarchy, Name conflict and Explicit Interface Implementation

Example of Interface Hierarchy
interface IWalkable
{
    void Walk();
}
interface IFlyable
{
    void Walk();
    void Fly();
}
interface IConflictable : IWalkable, IFlyable
{
    void GetNature();
}

class Animal : IConflictable
{
    void IFlyable.Fly()
    {
        Console.WriteLine("Bird can fly.");
    }

    void IConflictable.GetNature()
    {
        Console.WriteLine("Nature of cat and bird.");
    }

    void IWalkable.Walk()
    {
        Console.WriteLine("Cat can walk.");
    }

    void IFlyable.Walk()
    {
        Console.WriteLine("Bird can walk.");
    }
}
class Program
{
    static void Main(string[] args)
    {
        IConflictable animal = new Animal();
        animal.GetNature();
        animal.Fly();
        // animal.Walk(); // conflict because Walk in both IWalkable and IFlyable
        ((IWalkable)animal).Walk();
        ((IFlyable)animal).Walk();
    }
}
In above code, animal is declared as IConflictable.

IConflictable inherits both IWalkable and IFlyable, and both interfaces contain a Walk() method. Because of that, calling: animal.Walk(); is ambiguous — the compiler doesn't know whether you mean IWalkable.Walk() or IFlyable.Walk(). To resolve this issue, Explicit Interface Implementation is required. Therefore, both Walk() methods are implemented explicitly.

Then to call desired Walk() method, we must cast to the desired interface.

To call IFlyable.Walk():
IConflictable animal = new Animal();
((IFlyable)animal).Walk(); // Output: Bird can walk.
To call IWalkable.Walk():
IConflictable animal = new Animal();
((IWalkable)animal).Walk(); // Output: Cat can walk.

Saturday, June 27, 2026

C# Example of Array of Interface Type

Array of Interface Type: If classes A, B and C implements an interface I then an array of interface denoted by I[] can be initialized as follows
I[] arr = {new A(), new B(), new C()};
Example
interface IShape
{
    int NumberOfPoints { get; }
}
class Triangle : IShape
{
    int _numberOfPoints = 3;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Rectangle : IShape
{
    int _numberOfPoints = 4;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Hexagon : IShape
{
    int _numberOfPoints = 6;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Program
{
    static void Main(string[] args)
    {
        // array of interface where each element is an object,
        // which class implements the inetrface
        IShape[] shapes = { new Triangle(), new Rectangle(), new Hexagon() };
        foreach (var shape in shapes)
        {
            Console.WriteLine($"{shape.GetType()} has {shape.NumberOfPoints} points.");
        }
    }
}
OUTPUT
Triangle has 3 points.
Rectangle has 4 points.
Hexagon has 6 points.

C# Example of Interface as Method Return Type

Example of Interface as Method Return Type: If class implements an interface then the object of that class can be returned as method return type which is that interface. In the following example, GetShape method can return an object which class implements IShape interface. Here, Triangle, Rectangle and Hexagon classes implements IShape interface. Therefore, GetShape method can return an object of Triangle, Rectangle or Hexagon classes.
interface IShape
{
    int NumberOfPoints { get; }
}
class Triangle : IShape
{
    int _numberOfPoints = 3;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Rectangle : IShape
{
    int _numberOfPoints = 4;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Hexagon : IShape
{
    int _numberOfPoints = 6;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Shape
{
    public IShape GetShape(int count)
    {
        switch (count)
        {
            case 3:
                return new Triangle();
            case 4:
                return new Rectangle();
            case 6:
                return new Hexagon();
            default:
                throw new Exception("No shape found.");
        }
    }
    public void Info(int points)
    {
        IShape shape = GetShape(points);
        Console.WriteLine(shape.GetType() + " has " + shape.NumberOfPoints + " points.");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Shape shape = new Shape();
        shape.Info(3);
        shape.Info(4);
        shape.Info(6);
        shape.Info(7);
    }
}
OUTPUT
Triangle has 3 points.
Rectangle has 4 points.
Hexagon has 6 points.
Unhandled exception. System.Exception: No shape found.
 

C# Example of Interface as Method Parameter

Example of Interface as Method Parameter: If class implements an interface then the object of that class can be passed as argument to the method parameter which type is the interface.
interface IShape
{
    int NumberOfPoints { get; }
}
class Triangle : IShape
{
    int _numberOfPoints = 3;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Rectangle : IShape
{
    int _numberOfPoints = 4;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Hexagon : IShape
{
    int _numberOfPoints = 6;
    public int NumberOfPoints { get => _numberOfPoints; }
}
class Shape
{
    public void GetPointCount(IShape shape)
    {
        Console.WriteLine($"{shape.GetType()} has {shape.NumberOfPoints} number of points.");
    }
}
class Program
{
    static void Main(string[] args)
    {
        Shape shape = new Shape();
        shape.GetPointCount(new Triangle());
        shape.GetPointCount(new Rectangle());
        shape.GetPointCount(new Hexagon());
    }
}
OUTPUT
Triangle has 3 number of points.
Rectangle has 4 number of points.
Hexagon has 6 number of points.

C# How to copy an object with or without implementing ICloneable

Look at the implementation of ICloneable interface by Employee class to allow copying the employee objects.
class Employee : ICloneable
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
    public Employee(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public object Clone()
    {
        return new Employee(Name, Age);
    }
}
class Program
{
    static void Main(string[] args)
    {
        Employee e1 = new Employee("Ajeet", 32);
        Employee e2 = (Employee)e1.Clone();
        Console.WriteLine($"Name:{e1.Name}, Age:{e1.Age}");
        Console.WriteLine($"Name:{e2.Name}, Age:{e2.Age}");
    }
}
The Clone() method creates a new Employee object and copies the values:
public object Clone()
{
    return new Employee(Name, Age);
}
Since, string is immutable reference type and int is a value type, this effectively behaves like a deep enough copy for Employee class.
Example:
Employee e1 = new Employee("Ajeet", 32);
Employee e2 = (Employee)e1.Clone();

e2.Name = "Rahul";

Console.WriteLine(e1.Name); // Ajeet
Console.WriteLine(e2.Name); // Rahul

e1 remains unchanged because e2 is a different object.

Problems with ICloneable
But there are two design issues with ICloneable
1. Clone() returns object, so callers must cast:
Employee e2 = (Employee)e1.Clone();
This loses type safety.

2. ICloneable does not define shallow vs deep copy. Consumers cannot know whether:
Employee e2 = (Employee)e1.Clone();
creates shallow copy or deep copy. That ambiguity is why ICloneable is generally discouraged in modern C# APIs.

Preferred modern approach does not use ICloneable. Note that there does not exist generic ICloneable<T>. Modern C# mainly uses copy constructor or Clone method that returns specific type, which is shown in the following example.

Examples of copying object without ICloneable
Example1. Clone method returns strongly type object.
class Employee
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
    public Employee(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public Employee Clone()
    {
        return new Employee(Name, Age);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Employee e1 = new Employee("Ajeet", 32);
        Employee e2 = e1.Clone();
        Console.WriteLine($"Name:{e1.Name}, Age:{e1.Age}");
        Console.WriteLine($"Name:{e2.Name}, Age:{e2.Age}");
    }
}
Example2. Copy constructor
class Employee
{
    public string Name { get; set; } = string.Empty;
    public int Age { get; set; }
    public Employee(string name, int age)
    {
        Name = name;
        Age = age;
    }
    public Employee(Employee employee)
    {
        this.Name = employee.Name;
        this.Age = employee.Age;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Employee e1 = new Employee("Ajeet", 32);
        Employee e2 = new Employee(e1);
        Console.WriteLine($"Name:{e1.Name}, Age:{e1.Age}");
        Console.WriteLine($"Name:{e2.Name}, Age:{e2.Age}");
    }
}

C# Types that support Covariance and Contravariance

In this post, you will see which generic types support covariance and contra-variance and why.

Variance in C# (covariance out and contravariance in) is mainly supported on:

  • Generic interfaces
  • Generic delegates

It is not supported on generic classes or structs.

Examples:

Interface supports covariance and contravariance
interface IProducer<out T>   // covariance
{
    T Get();
}
interface IConsumer<in T>    // contravariance
{
    void Put(T item);
}

Delegate supports covariance and contravariance
delegate T Factory<out T>();
delegate void Processor<in T>(T value);

Generic Class does not support covariance and contravariance
class Box<out T> // Compile-time error
{
}

Generic Struct does not allow covariance and contravariance
struct Container<out T>   // Compile-time error
{
}

Why classes/structs don't support variance?
Classes are usually both producers and consumers of T.
Example:
class Box<T>
{
    public T Value { get; set; }
}
If covariance were allowed:
Box<Dog> dogs = new Box<Dog>();
Box<Animal> animals = dogs;   // imagine this were legal
animals.Value = new Cat();    // valid for Box<Animal>

Now dogs contains a Cat, which breaks type safety.

That is why C# restricts variance to interfaces/delegates where the compiler can enforce rules:

  • out T → only output positions (return values)
  • in T → only input positions (parameters)
Arrays are a special exception
Arrays are covariant even though they behave like classes:
string[] names = new string[2];
object[] objects = names;   // allowed
objects[0] = 10;            // Runtime exception
This compiles but throws:
ArrayTypeMismatchException

Generic variance was designed to avoid such runtime problems and provide type safety.

So the rule is:

Type Covariance/Contravariance
Interface supports
Delegate supports
Class does not support
Struct does not allow
Array supports (special case, runtime checked)

Note. The generic type parameter itself does not have to be constrained as where T : class; the actual substituted type must be a reference type for variance conversion to occur.

C# Invariance, Covariance and contravariance Explained

Covariance and Contravariance in C#
Generic interface was introduced in C# version 2.0 and Generic variance - covariance and contravariance - was introduced in C# version 4.0. The out or in modifier with type parameter is used to convert invariant generic type into covariant and contravariant generic respectively. By default a generic type is invariant.

Covariance and contravariance in C# define how type safety and assignment compatibility behave when you pass derived types (subclasses) or base types (superclasses) to generic type parameters, arrays, and delegates. The above concept is abstract and abstruse. Let's understand this concept with the help of an example. 

Consider the following generic interface IContract.
interface IContract<T>
{
    // Note: The position of T determines if it can be covariant or     contravariant
    void Print(T data);
}

Points to Note

  • IContract<T> is an open generic interface because it contains a type parameter T.
  • IContract<string> is a closed generic interface because it contains a type argument - string for type parameter T.
  • Similarly, IContract<int> and IContract<object> are also closed generic interfaces because they contain type arguments - int and object respectively.
Inheritance Relationship between Type Arguments
Note. Throughout the discussion, we assume that:
  • closed generic types belong to the same open generic type.
  • the type arguments are of reference types.
Now, consider the inheritance relationship between type arguments of two closed generic interfaces. For example, IEnumerable<Animal> and IEnumerable<Dog> are closed generic interfaces, and assuming that Dog is a derived class of the Animal class, we can see the inheritance relationship between type arguments of two closed generic interfaces.
  • Every Dog is an Animal.
  • So, every sequence of dogs can be treated as a sequence of animals.
  • So, we can assign a list of dogs to a sequence of animals.
  • That is, IEnumerable<Animal> animals = new List<Dog>(); (This works because IEnumerable<out T> is covariant. Note that a literal List<T> is invariant, so you cannot do List<Animal> list = new List<Dog>();).
We have discussed so far about:
  • open generic interface
  • closed generic interface
  • relationship between type arguments of two closed generic interfaces of the same open generic interface
  • assignment compatibility
Now we discuss invariance, covariance and contravariance.
Invariance, Covariance and Contravariance
The concepts of invariance, covariance and contravariance are involved with closed generics of the same open generic types. Different objects can be created out of closed generics. For example, assuming ContractImpl<T> is a class that implements IContract<T>:
IContract<object> contract1 = new ContractImpl<object>();
IContract<string> contract2 = new ContractImpl<string>();

IEnumerable<Animal> animals = new List<Dog>();

Now the question is, can we use contract1 = contract2; or contract2 = contract1; because both belong to the same open generic IContract<T>?
Whether contract1 = contract2; or contract2 = contract1; is allowed or not depends on defining covariance and contravariance in the open generic IContract<T>.

For example, IContract<T> can be defined as:

  • IContract<out T> for covariance (where T is only used as an output/return type)
  • IContract<in T> for contravariance (where T is only used as an input parameter)
  • The out modifier denotes covariance and the in modifier denotes contravariance.
Interface definition for Covariance
interface IContract<out T>
    void Print(T data);
}
Interface definition for Contravariance
interface IContract<in T>
    void Print(T data);
}
Interface definition for Invariance
interface IContract<T>
    void Print(T data);
}

Note. Looking at the definition of open generic interface, we can know the nature of type parameter i.e. convariance, contravariance or invariance.

The out modifier example (Covariance):
A reference variable which datatype is "closed generic type of supertype" can be assigned an object or reference variable which datatype is "closed generic type of a subtype". For example, a list of dogs assigned to a sequence of animals: 

IEnumerable<Animal> animals = new List<Dog>();

Here the output is a sequence of animals as per the IEnumerable<out T> defined in generic IEnumerable. It means that T can be a return type (output) of an open generic method, enabling a more derived type to be substituted where a less derived type is expected.

Mnemonics: Supertype substituted by Subtype.

Example of Covariance

// definition of open generic interface with out modifier
interface IContract<out T>
{
    T Print();
}
// open generic class implementing open generic interface
class ContractImpl<T> : IContract<T>
{
    public T Print()
    {
        return T
    }
}

class Program
{
    static void Main()
    {
        // closed generics
	IContract<object> objPrinter = new ContractImpl<object>();
    
        // Allowed because of `in`
        IContract<string> strPrinter = objPrinter;

        strPrinter.Print("Hello");
    }
}
The in modifier example (Contravariance):
A reference variable which datatype is "closed generic type of subtype" can be assigned an object or reference variable which datatype is "closed generic type of a supertype". For example, if we have IContract<in T> where T is an input (e.g., void Print(T data);), we can do: 

IContract<Dog> dogContract = new ContractImpl<Animal>();

This is safe because a contract that knows how to handle any general Animal can safely handle a specific Dog when passed as an input.

Example of Contravariance

// definition of open generic interface with out modifier
interface IContract<out T>
{
    T Print();
}
// open generic class implementing open generic interface
class ContractImpl<T> : IContract<T>
{
    private readonly T _value;
    public ContractImpl(T value)
    {
        _value = value;
    }
    public T Print()
    {
        Console.WriteLine($"Type is {typeof(T).FullName}");
        return _value;
    }
}
class Animal
{
    string _name;
    public Animal(string name)
    {
        _name = name;
    }
    public virtual void Info()
    {
        Console.WriteLine($"Animal: Name={_name}");
    }
}
class Dog : Animal
{
    string _name;
    int _age;
    public Dog(string name, int age) : base(name)
    {
        _name = name;
        _age = age;
    }
    public override void Info()
    {
        Console.WriteLine($"Dog: Name={_name}, Age={_age}");
    }
}
class Program
{
    static void Main()
    {
        // 1. Create a Dog contract instance
        IContract<Dog> dogContract = new ContractImpl<Dog>(new Dog("Rocky", 4));

        // ==============================================================
        // 2. DEMONSTRATING COVARIANCE (The power of the 'out' modifier)
        // ==============================================================
        // This assignment is ONLY possible because of 'out T' in IContract<out T>.
        // If you remove 'out' from the interface, this line will crash the compiler!
        IContract<Animal> animalContract = dogContract;

        Console.WriteLine("--- Covariance Demo ---");

        // 3. You can now use the animalContract seamlessly
        Animal animal = animalContract.Print();
        animal.Info(); // Invokes Dog's overridden Info() via polymorphism


        // ===============================================================
        // 4. Practical Real-World Use Case: Passing it to a Method
        // ===============================================================
        Console.WriteLine("\n--- Method Parameter Demo ---");

        // We can pass our dogContract directly into a method expecting an animal contract
        ProcessAnimalContract(dogContract);
    }

    // This method expects an IContract of Animal, but thanks to covariance, 
    // it happily accepts IContract of Dog, Cat, etc.
    static void ProcessAnimalContract(IContract<Animal> contract)
    {
        Animal a = contract.Print();
        Console.WriteLine($"Successfully processed contract for an animal.");
    }
}
Note. We use in or out modifier in open generic type when the type is defined. But we omit in or out modifier in the closed generic types. For example, in IEnumerable<out T> definition:
namespace System.Collections.Generic
{
    public interface IEnumerable<out T> : IEnumerable
    {
        IEnumerator<T> GetEnumerator();
    }
}

But in closed generic type, we omit modifier(in or out):
class Zoo{
    // Method returns IEnumerable<Animal>
    public IEnumerable<Animal> GetAnimals()
    {
        List<Dog> dogs = new List<Dog>
        {
            new Dog { Name = "Tommy" },
            new Dog { Name = "Bruno" }
        };
        // Allowed because IEnumerable<out T> is covariant
        return dogs;
    }
}

Note. The IEnumerator<out T> is also case of covariance.
namespace System.Collections.Generic
{
    public interface IEnumerator<out T> : IEnumerator, IDisposable
    {
        T Current { get; }
    }
}
Invariance
In case of invariance, the type arguments are not considered to be related in any hierarchy. We cannot use what we can do in case of covariance or contravariance.

If a generic type parameter is used as both an input and an output, it must be invariant to preserve type safety at runtime.For example, List<T> is invariant. It allows you to both add items (input) and read items (output). 

Invariance means you must use the exact type specified; it can neither be converted up nor down the inheritance tree. For example,

// This will cause a COMPILE ERROR:
List<Animal> animals = new List<Dog>(); 

Why does the above code throw compile time error? If the code above compiled, you could theoretically execute animals.Add(new Cat());. This would break type safety because a Cat would be injected into what is actually a backing array of Dog objects.

Example of Invariance
The same previous example without in or out modifier makes the case of invariance.
// definition of open generic interface with out modifier
interface IContract<T>
{
    T Print();
}
// open generic class implementing open generic interface
class ContractImpl<T> : IContract<T>
{
    private readonly T _value;
    public ContractImpl(T value)
    {
        _value = value;
    }
    public T Print()
    {
        Console.WriteLine($"Type is {typeof(T).FullName}");
        return _value;
    }
}
class Animal
{
    string _name;
    public Animal(string name)
    {
        _name = name;
    }
    public virtual void Info()
    {
        Console.WriteLine($"Animal: Name={_name}");
    }
}
class Dog : Animal
{
    string _name;
    int _age;
    public Dog(string name, int age) : base(name)
    {
        _name = name;
        _age = age;
    }
    public override void Info()
    {
        Console.WriteLine($"Dog: Name={_name}, Age={_age}");
    }
}
class Program
{
    static void Main()
    {
        IContract<Dog> dogContract = new ContractImpl<Dog>(new Dog("Rocky", 4));
        // 1. Case of subtype to supertype
        // Cannot assign Dog type argument to Animal type argument generic
        // IContract<Animal> dogToAnimal = dogContract; // compile time error

        // 2. Case of supertype to subtype
        IContract<Animal> animalContract = new ContractImpl<Animal>(new Animal("Tommy"));
        // Cannot assign Animal type argument to Dog type argument generic
        // dogContract = animalContract; // compile time error

        // 3. call methods
        Dog dog = dogContract.Print();
        dog.Info();
        Animal animal = animalContract.Print();
        animal.Info();
    }

}
Note that subtype or supertype type substitution is not allowed in case of invariance.

Key Limitations to Keep in Mind
Reference Types Only: Variance rules only apply to reference types. If you use a value type (like int, struct, or bool), the behavior defaults to completely invariant.

Array Covariance Gotcha: Arrays have been covariant since C# 1.0 (e.g., object[] array = new string[10];). However, this implementation is unsafe because it bypasses compile-time checks and will throw an ArrayTypeMismatchException at runtime if you try to insert an invalid type. Always prefer generic collections like IEnumerable<T> to enforce safety at compile time.

C# Understanding Default Interface Method in Interface type and how to call it

In this post, you will understand about default interface method in an interface type. For this, look at the following code and answer this question: How to call default interface method Print of IContract interface in Program class?
interface IContract
{
    void Apply();
    void Print()
    {
        Console.WriteLine("Print Default implementation");
    }
}
class Implementer : IContract
{
    public void Apply()
    {
        Console.WriteLine("Implementer implemented Apply");
    }
    public void Print()
    {
        Console.WriteLine("Implementer implemented Print");
    }

    class Program
    {
        static void Main(string[] args)
        {
            Implementer implementer = new Implementer();
            implementer.Apply(); // Implementer implemented Apply
            implementer.Print(); // Implementer implemented Print
            IContract contract = implementer;
            contract.Print(); // Implementer implemented Print
        }
    }
}
Answer: Since default interface method Print is implemented in Implementer class, calling Print will invoke class implementation, not interface implementation. If you really want to invoke interface method implementation then class should not re-implement the default interface method implementation.
Soultion:
interface IContract
{
    void Apply();
    void Print()
    {
        Console.WriteLine("Print Default implementation");
    }
}
class Implementer : IContract
{
    public void Apply()
    {
        Console.WriteLine("Implementer implemented Apply");
    }

    class Program
    {
        static void Main(string[] args)
        {
            Implementer implementer = new Implementer();
            implementer.Apply(); // Implementer implemented Apply
            IContract contract = implementer;
            contract.Print(); // Print Default implementation
        }
    }
}
Since C# version 8 onward, method implementation is allowed in an interface type. The class implementing this interface cannot directly access this method. It means that class reference variable cannot directly acces this method.
implementer.Print(); // Invalid access

The compiler message is: "'Implementer' does not contain a definition for 'Print' and no accessible extension method 'Print' accepting a first argument of type 'Implementer' could be found (are you missing a using directive or an assembly reference?)"

But the interface reference variable pointing to the object of implementing class can access this method.
contract.Print(); // Print Default implementation

C# Evolution of Interface type over course of time

Interfaces in C# started as a pure contract type and gradually gained more capabilities across language versions. Here’s a timeline of the major additions.

C# Version Interface Feature Added Example
C# 1.0 (2002) Basic interfaces Method/property/event/indexer declarations
C# 2.0 Generic interfaces IEnumerable<T>
C# 3.0 No major interface changes
C# 4.0 Generic variance out, in
C# 5–7.x Minor improvements Expression-bodied members support
C# 8.0 Default interface implementations Method body inside interface
C# 8.0 Static members allowed (with bodies) Static helper methods
C# 8.0 Access modifiers inside interface private, protected, etc.
C# 11 Static abstract members Generic math
C# 11 Static virtual members Default static behavior

1. C# 1.0 — Interfaces as pure contracts

  • Only declarations were allowed.
  • No fields, no implementation.
  • abstract methods, properties, events and indexers
  • Implementation required in implementing class/struct

interface IShape
{
    void Draw();
    int Points { get; }
}
class Circle : IShape
{
    public int Points => 0;
    public void Draw()
    {
    }
}


2. C# 2.0 — Generic interfaces

  • Interfaces became type-safe.
  • Type parameter introduced with interface type and its members

interface IRepository<T>
{
    void Add(T item);
}

Example:
IEnumerable<int>
IComparer<string>


3. C# 4.0 — Variance (in, out)

  • Allowed covariance and contravariance.
  • in and out modifier with type parameter
  • The out modifier tells that the type parameter is return type
  • The in modifier tells that the type parameter is method parameter type
  • If in or out modifier is omitted  then type parameter can be (return type)/(parameter type)
Covariant (out) → return types:
interface IProducer<out T>
{
    T Create();
}

Contravariant (in) → parameter types:
interface IConsumer<in T>
{
    void Consume(T item);
}


4. C# 8.0 — Default Interface Methods (major change)
  • Interfaces could finally contain implementations.
  • This will be default implementation for implementers(class/struct)
interface ILogger
{
    void Log(string msg)
    {
        Console.WriteLine(msg);
    }
}
Implementers can inherit default behavior:
class FileLogger : ILogger
{
}

This helped version interfaces without breaking existing code.

5. C# 8.0 — Access modifiers in interfaces
Before C# 8:
interface ITest
{
void Show(); // implicitly public
}

After C# 8:
interface ITest
{
    public void Show()
    {
    }
    private void Helper()
    {
    }
}

👉public interface members became valid only once interfaces could contain implementations.

6. C# 8.0 — Static members in interfaces

interface IMath
{
    static int Add(int a, int b)
    {
        return a + b;
    }
}

Used like: IMath.Add(10, 20);

7. C# 11 — Static abstract members (very important)

  • Interfaces can require static members.
interface IAddable<T>
{
    static abstract T operator +(T a, T b);
}

Implementation:

struct Number : IAddable<Number>
{
    public int Value;
    public static Number operator +(Number a, Number b)
    => new Number { Value = a.Value + b.Value };
}

👉This enabled generic math.

8. C# 11 — Static virtual members
interface IExample
{
    static virtual int Value => 100;
}
Allows default static behavior.

Overall evolution

  1. Contract only(abstract declarations of method/property/event/indexer)
  2. Generic contracts(introduced type parameter T) 
  3. Variance support (in and out modifiers)
  4. Interfaces with behavior (default methods and access modifier with members)
  5. Static polymorphism (generic math)
So modern C# interfaces are much more powerful than the original “only abstract members” design.

 

Friday, June 26, 2026

C# Examples of switch statements and switch expressions

Instead of using multiple if conditions, switch statement can be used to make code more readable. 
Some Facts and features of switch case:
  • The switch statement does not return any value.
  • The switch keyword is followed by an expression inside parentheses.
  • The expression is evaluated for its value or type etc. with case label.
  • Inside switch block, there are case statements.
  • Each case is followed by a label. 
    • The label can be constant or pattern.
    • The label can be either a literal value e.g. 1, 4, 7.8, true, false, tuple values etc. 
    • Or, it can be a data type e.g. int, float, double etc.
    • Or, it can be data type followed by a variable for that type.
    • Or, it can be data type followed by a variable of that type and constraint on the variable using when clause.
  • Each case must end with a jump statement (break, return, throw, etc).
  • The break statement is not mandatory in switch statement. Instead, you can use return or throw statement also.
  • The default keyword is used for default case. It represents the case not found in other cases. It can be placed any order. It should not necessarily be the last case.
  • The default case is optional.
  • You can also pass delegate variable in switch expression.
In brief, a switch statement executes one matching case block. Cases can use constants or patterns. Each case must end with a jump statement (break, return, throw, etc.) to prevent fall-through. default is optional and may appear anywhere.

List of Examples
  • Example. Case exact matching constant (constant pattern match)
  • Example. Case of inexact match with relational operator(>,<,>=,<=) (constant pattern match)
  • Example. Case of matching with not operator(constant pattern match)
  • Example. Case of matching string type and its value(constant pattern match)
  • Example. Case with Boolean value(constant pattern match)
  • Example. Case with different datatypes(type pattern match)
  • Example. Case matching value and its datatype (pattern match)
  • Example. Case matching value and its datatype plus constraint on value (pattern match)
  • Example. Case matching value tuple (constant match)
  • Example. switch expression with lambda literal (pattern match)
  • Example. switch expression with lambda literal to match value tuple
  • Example. switch with delegate object, check if delegate exists
  • Example. switch with delegate invoked, (constant match)
  • Example. switch with delegate object (delegate type match)

Example. Case exact matching constant (constant pattern match)
class Demo
{
    public void Guess(int number)
    {
        switch (number)
        {
            case 1:
                Console.WriteLine("Value is 1");
                Console.WriteLine("Some more statements for 1.");
                break; // required
            case 2:
            case 3:
                Console.WriteLine("Value is 2 or 3");
                break; // required
            default:
                Console.WriteLine("Value did not match.");
                break; // required
        }
    }
}
class Program
{
    Random rand = new Random();
    static void Main(string[] args)
    {
        Program p = new();
        Demo demo = new();
        demo.Guess(p.rand.Next(1, 10));
    }
}

Example. Case of inexact match with relational operator(>,<,>=,<=) (constant pattern match)
class Demo
{
    public void Guess(int number)
    {
        switch (number)
        {
            case >= 10:
                Console.WriteLine("Greater than 10");
                break;

            case < 0:
                Console.WriteLine("Negative");
                break;

            case 5:
                Console.WriteLine("Exactly 5");
                break;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Program p = new();
        Demo demo = new();
        demo.Guess(20);   // Greater than 10
        demo.Guess(-2);   // Negative
        demo.Guess(7);    // Not 5
        demo.Guess(5);    // Exactly 5
    }
}

Example. Case of matching with not operator(constant pattern match)
class Demo
{
    public void Guess(int number)
    {
        switch (number)
        {
            case not 5:
                Console.WriteLine("Other than 5");
                break;
            case 5:
                Console.WriteLine("Five");
                break;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Program p = new();
        Demo demo = new();
        demo.Guess(20);   // Other than 5
        demo.Guess(5);    // Five
    }
}
Example. Case of matching string type and its value(constant pattern match)
class Demo
{
    public void Guess(string animal)
    {
        switch (animal)
        {
            case "Crow":
                Console.WriteLine("Crow is a bird");
                break; // required
            case "Cat":
            case "Dog":
                Console.WriteLine("Cat or Dog");
                break; // required
            default:
                Console.WriteLine("Not an animal.");
                break; // required
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new();
        demo.Guess("Dog");
        demo.Guess("Robert");
    }
} 
Example. Case with Boolean value(constant pattern match)
class Demo
{
    public void Guess(bool success)
    {
        switch (success)
        {
            case true:
                Console.WriteLine("Truth is life.");
                break; // required
            case false:
                Console.WriteLine("Life is hell for falsehood.");
                break; // required
            // default is optional, here not required
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new();
        demo.Guess(false);
        demo.Guess(true);
    }
}
Example. Case with different datatypes(type pattern match)
class Demo
{
    public void Guess(object data) // boxing
    {
        switch (data) // data should be primitive type
        {
            case int:
                Console.WriteLine("Integer type.");
                break; // required
            case float:
                Console.WriteLine("Float type.");
                break; // required
            case double:
                Console.WriteLine("Double type.");
                break; // required
            case bool:
                Console.WriteLine("Boolean type.");
                break; // required
                       // default is optional, here not required
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new();
        demo.Guess(11);
        demo.Guess(11.12F);
        demo.Guess(11.12);
        demo.Guess(true);
    }
}

Example. Case matching value and its datatype (pattern match)
class Demo
{
    public void Guess(object data) // boxing
    {
        switch (data) // data should be primitive type
        {
            case int x:
                Console.WriteLine($"Integer type value is {x}.");
                break; // required
            case float x:
                Console.WriteLine($"Float type value is {x}.");
                break; // required
            case double x:
                Console.WriteLine($"Double type value is {x}.");
                break; // required
            case bool x:
                Console.WriteLine($"Boolean type value is {x}.");
                break; // required
                       // default is optional, here not required
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new();
        demo.Guess(11);
        demo.Guess(11.12F);
        demo.Guess(11.12);
        demo.Guess(true);
    }
}
Example. Case matching value and its datatype plus constraint on value (pattern match)
class Demo
{
    public void Guess(object data) // boxing
    {
        switch (data) // data should be primitive type
        {
            case int x when x + 5 > 10:
                Console.WriteLine($"Integer type value is {x}.");
                break; // required
            case float x when x > 10.15F:
                Console.WriteLine($"Float type value is {x}.");
                break; // required
            case double x when x > 1.0:
                Console.WriteLine($"Double type value is {x}.");
                break; // required
            case bool x:
                Console.WriteLine($"Boolean type value is {x}.");
                break; // required
                       // default is optional, here not required
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new();
        demo.Guess(6);
        demo.Guess(1.12F);
        demo.Guess(1.12);
        demo.Guess(true);
    }
}

Example. Case matching value tuple (constant match)
class Demo
{
    public void Guess((int, int) data)
    {
        switch (data)
        {
            case (1, 1):
                Console.WriteLine($"Coordinate x and y are: {data.Item1} and {data.Item2}");
                break;
            case (2, 1):
                Console.WriteLine($"Coordinate x and y are: {data.Item1} and {data.Item2}");
                break;
            case (1, 2):
                Console.WriteLine($"Coordinate x and y are: {data.Item1} and {data.Item2}");
                break;
            default:
                Console.WriteLine("No tuple matched!");
                break;
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new();
        demo.Guess((1, 12));
        demo.Guess((1, 2));
    }
}

Example. switch expression with lambda literal (pattern match)
class Demo
{
    public void Guess(int input)
    {
        string result = input switch
        {
            1 => $"The output for 1 is {input + 10}",
            2 => $"The output for 2 is {input + 20}",
            _ => "No match found",
        };
        Console.WriteLine(result);
    }
}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new();
        demo.Guess(1);
        demo.Guess(2);
        demo.Guess(3);
    }
}
Example. switch expression with lambda literal to match value tuple
class Demo
{
    public void Guess((int, int) input)
    {
        string result = input switch
        {
            (1, 1) => $"{input.Item1} == {input.Item2}",
            (1, 2) => $"{input.Item1} < {input.Item2}",
            (2, 1) => $"{input.Item1} > {input.Item2}",
            _ => "No match found",
        };
        Console.WriteLine(result);
    }
}
class Program
{
    static void Main(string[] args)
    {
        Demo demo = new();
        demo.Guess((1, 1));
        demo.Guess((1, 2));
        demo.Guess((3, 2));
    }
}
Example. switch with delegate object, check if delegate exists
You can pass a delegate variable as the switch expression.

class Demo
{
    public void Test(Action d)
    {
        switch (d)
        {
            case null:
                Console.WriteLine("Delegate is null");
                break;

            default:
                Console.WriteLine("Delegate exists");
                break;
        }
    }
}
class Program
{
    Action someAction = () => Console.WriteLine("Hello");
    static void Main(string[] args)
    {
        Program p = new();
        Demo demo = new();
        demo.Test(p.someAction);
    }
}
Example. switch with delegate invoked, (constant match)
class Demo
{
    public void Test(Func<int> d)
    {
        switch (d())   // invoke delegate
        {
            case 10:
                Console.WriteLine("Ten");
                break;

            case > 10:
                Console.WriteLine("Greater than 10");
                break;
        }
    }
}
class Program
{
    Func<int> someDelegate = () => 10;
    static void Main(string[] args)
    {
        Program p = new();
        Demo demo = new();
        demo.Test(p.someDelegate);
    }
}
Example. switch with delegate object (delegate type match)
class Demo
{
    public void Test(Delegate d)
    {
        switch (d)
        {
            case Action:
                Console.WriteLine("Action delegate");
                break;

            case Func<int>:
                Console.WriteLine("Func<int>");
                break;
            default:
                break;
        }
    }
}
class Program
{
    Delegate d = (Action)(() => { });
    static void Main(string[] args)
    {
        Program p = new();
        Demo demo = new();
        demo.Test(p.d);
    }
}
==

C# Nullable reference types and Operators on Nullable Types

In this post we learn about:
  • Nullable reference types
  • Non-nullable reference types
Nullable reference types: They may have null value and they are declared similar to nullable value types by using nullable annotation(?) after data type.
Example
string? FirstName;
They must be initialized before using it.
string? FirstName = null;

Non-nullable reference types: They must be assigned a non-null value at initialization and cannot be later changed to a null value.
Example
string Name = String.Empty;

Enabling Nullable reference types Feature in Visual Studio
You can enable/disable nullable reference type features in project file using enable/disable/warning values. The current settings uses enable value:
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
  </PropertyGroup>

</Project>

Operating on Nullable Types
C# provides several operators for working with nullable types. They are
  • the null-coalescing operator, 
  • the null-coalescing assignment operator, and 
  • the null conditional operator.

The null-coalescing operator
Look at the following code. The null-coalescing operator(??) returns the second operand if first operand is null else it returns the first operand.

string? FirstName = null;
var result = FirstName ?? ThisValueIfFirstNameIsNull;

Example of traditional approach would be:
class Test
{
    public string? FirstName = null;
    string result = string.Empty;
    public string GetName()
    {
        if (FirstName != null)
        {
            result = FirstName;
        }
        else
        {
            result = "ThisValueIfFirstNameIsNull";
        }
        return result;
    }
}
class Program
{
    static void Main(string[] args)
    {
        Test test = new Test();
        test.FirstName = "Ajeet";
        Console.WriteLine(test.GetName());
        test.FirstName = null;
        Console.WriteLine(test.GetName());
    }
}
The  null-coalescing operator(??) provides concise solution for the same:
class Test
{
    public string? FirstName = null;
    string result = string.Empty;
    public string GetName()
    {
        return FirstName ?? "ThisValueIfFirstNameIsNull";
    }
}
class Program
{
    static void Main(string[] args)
    {
        Test test = new Test();
        test.FirstName = "Ajeet";
        Console.WriteLine(test.GetName());
        test.FirstName = null;
        Console.WriteLine(test.GetName());
    }
}
The null-coalescing assignment operator
Look at the following code. The null-coalescing assignment operator(??) assigns the second operand to first one if first operand is null and then returns else it simply returns the first operand.

string? FirstName = null;
FirstName ??= ThisValueIfFirstNameIsNull;

Example of traditional Approach would be:
class Test
{
    public string? FirstName = null;
    public string GetName()
    {
        if (FirstName != null)
        {
            return FirstName;
        }
        else
        {
            return "ThisValueIfFirstNameIsNull";
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        Test test = new Test();
        test.FirstName = "Ajeet";
        Console.WriteLine(test.GetName());
        test.FirstName = null;
        Console.WriteLine(test.GetName());
    }
}
The  null-coalescing assignment operator(??=) provides concise solution for the same:
class Test
{
    public string? FirstName = null;
    public string GetName()
    {
        return FirstName ??= "ThisValueIfFirstNameIsNull";
    }
}
class Program
{
    static void Main(string[] args)
    {
        Test test = new Test();
        test.FirstName = "Ajeet";
        Console.WriteLine(test.GetName());
        test.FirstName = null;
        Console.WriteLine(test.GetName());
    }
}
The null-coalescing assignment operator
The null conditional operator(?.) evaluates the expression if first expression is not null else it ignores the expression:

expression1?.expression2
  • The expression2 is generally a property or method of an object.
  • The null conditional operator(?.) helps to avoid null reference exception at runtime.
Example
class Test
{
    public int? GetLength(string? name)
    {
        return name?.Length; // possiibility of NullReferenceException without ?.
    }
}
class Program
{
    string? first = null;
    string? last = "Kumar";
    int? result;
    static void Main(string[] args)
    {
        Program obj = new Program();
        Test test = new Test();
        obj.result = test.GetLength(obj.first);
        obj.result = test.GetLength(obj.last);
        Console.WriteLine(obj.result);
    }
}

Hot Topics