Monday, 3 November 2025

What's new in c# 14?



C# 14 — A Leap Toward Cleaner, Smarter Code

Exploring the most impactful features with practical examples

C# 14 marks a thoughtful evolution in the language, focusing on expressiveness, modularity, and developer productivity. While not a radical overhaul, this release introduces subtle yet powerful enhancements that make everyday coding more intuitive and maintainable. Whether you're building high-performance systems or crafting elegant APIs, C# 14 helps you write less boilerplate and more meaningful logic.

In this guide, we'll explore the most impactful features of C# 14, with practical examples and insights into how they improve real-world development.

๐Ÿงญ C# 14 Feature Highlights

Here are the key features we'll be diving into:

  1. Extension Members — Add properties and indexers to existing types via extension blocks.
  2. Field Keyword in Properties — Access auto-generated backing fields directly with field.
  3. Null-Conditional Assignment (??=) — Assign only when the target is null.
  4. Unbound Generic Types in nameof — Use open generic types for cleaner diagnostics.
  5. Implicit Span Conversions — Seamless transitions between Span<T> and ReadOnlySpan<T>.
  6. Lambda Parameter Modifiers — Use ref, in, or out in lambda expressions.
  7. Partial Events and Constructors — Modularize event and constructor logic across files.
  8. User-Defined Compound Assignment Operators — Customize behavior for +=, -=, etc.
  9. Improved Interpolated Strings — Enhanced formatting and performance for string interpolation.
  10. Collection Expressions (Preview) — Concise syntax for initializing collections.

1. Extension Members

๐Ÿ”ง What Are Extension Members?

Traditionally, C# extension methods let you "add" methods to existing types without modifying them. But with C# 14, you can now define:

  • Properties
  • Indexers
  • Events
  • Even operators

…all inside an extension block scoped to a specific type.

๐Ÿงช Example: Extension Methods for IEnumerable<T>

public static class EnumerableExtensions
{
    // Method: Check if the collection is empty
    public static bool IsEmpty<T>(this IEnumerable<T> source)
        => !source.Any();

    // Method: Access element by index safely (returns default if out of range)
    public static T? ElementAtOrDefaultSafe<T>(this IEnumerable<T> source, int index)
        => source.Skip(index).DefaultIfEmpty(default!).FirstOrDefault();

    // Method: Get the last element safely
    public static T? LastOrDefaultSafe<T>(this IEnumerable<T> source)
        => source.LastOrDefault();
}

๐Ÿ’ก Usage Example

var numbers = new[] { 1, 2, 3, 4, 5 };

// Using the extension methods
if (!numbers.IsEmpty())
{
    Console.WriteLine($"Third element: {numbers.ElementAtOrDefaultSafe(2)}");
    Console.WriteLine($"Last element: {numbers.LastOrDefaultSafe()}");
}

๐Ÿ’ก Key Benefits:

  • Cleaner API surface — Access properties and indexers naturally without method calls
  • Better IntelliSense — IDE suggestions now include extension properties and indexers
  • Type safety — Compile-time checks ensure proper usage

2. Field Keyword in Properties

๐Ÿ”‘ Direct Access to Backing Fields

The new field keyword allows you to access the auto-generated backing field directly in property accessors, eliminating the need to manually declare private fields.

๐Ÿงช Example: Smart Properties with Validation

public class Product
{
    public string Name { get; set; }

    public decimal Price
    {
        get => field;
        set
        {
            if (value < 0)
                throw new ArgumentException("Price cannot be negative");
            field = value;
        }
    }

    public int Quantity
    {
        get => field;
        set
        {
            field = Math.Max(0, value); // Ensure non-negative
            Console.WriteLine($"Stock updated: {field} units");
        }
    }
}

๐Ÿ’ก Why This Matters: Before C# 14, you had to declare a separate backing field and manually manage it. Now, you can add validation and logging to auto-properties without the boilerplate!

3. Null-Conditional Assignment (??=)

๐ŸŽฏ Assign Only When Null

The ??= operator assigns a value to a variable only if it's currently null. This simplifies lazy initialization patterns.

private Logger? _logger;

public Logger Logger
{
    get
    {
        // Initialize only if null
        _logger ??= new Logger();
        return _logger;
    }
}

Comparison with traditional approach:

// Old way
if (_logger == null)
{
    _logger = new Logger();
}

// New way
_logger ??= new Logger();

4. Unbound Generic Types in nameof

๐Ÿ“ What Are Unbound Generic Types?

Prior to C# 14, you couldn't use nameof with open generic types directly. You had to specify type parameters even when you just wanted the type's name. Now, you can reference unbound generic types cleanly.

๐Ÿงช Example: Cleaner Type Name References

// Before C# 14 - Had to provide type arguments
string typeName = nameof(Dictionary<string, int>); // "Dictionary"

// C# 14 - Can use unbound generic types
string typeName = nameof(Dictionary<,>); // "Dictionary"
string listName = nameof(List<>);       // "List"

๐Ÿ’ก Real-World Usage: Generic Factory with Logging

public class GenericFactory<T> where T : new()
{
    public T Create()
    {
        // Log the factory name without specifying T
        var factoryName = nameof(GenericFactory<>);
        Console.WriteLine($"Creating instance in {factoryName}");
        
        return new T();
    }
}

// Usage in error handling
public void ProcessRepository<TRepo, TEntity>()
{
    try
    {
        // Processing logic...
    }
    catch (Exception ex)
    {
        // Clean error messages without type parameters
        throw new InvalidOperationException(
            $"Failed in {nameof(ProcessRepository<,>)}", ex);
    }
}

๐Ÿ’ก Key Benefits:

  • Cleaner code — No need to provide arbitrary type arguments just to get a name
  • Better refactoring — Generic type names update automatically when renamed
  • Improved diagnostics — More readable error messages and logs
  • API documentation — Reference generic types clearly in generated docs

๐Ÿ“Š Use Cases

  • Logging: Log generic type names without concrete type parameters
  • Diagnostics: Create meaningful exception messages referencing generic methods
  • Reflection: Build type metadata utilities more elegantly
  • Code generation: Generate code based on generic type names

5. Implicit Span Conversions

Seamless Span Type Transitions

C# 14 introduces implicit conversions between Span<T> and ReadOnlySpan<T>, making it much easier to work with high-performance memory operations. You can now pass Span<T> to methods expecting ReadOnlySpan<T> without explicit casting.

๐Ÿงช Example: Before vs. After

// Before C# 14 - Manual casting required
void ProcessData(ReadOnlySpan<byte> data)
{
    // Process read-only data
    Console.WriteLine($"Processing {data.Length} bytes");
}

Span<byte> buffer = new byte[1024];
ProcessData(buffer); // ❌ Compiler error in older versions

// Had to explicitly cast:
ProcessData((ReadOnlySpan<byte>)buffer);

// C# 14 - Implicit conversion
Span<byte> buffer = new byte[1024];
ProcessData(buffer); // ✅ Works seamlessly!

๐Ÿ’ก Real-World Example: Stream Processing

public class DataProcessor
{
    // Method that reads data without modifying it
    public int CalculateChecksum(ReadOnlySpan<byte> data)
    {
        int checksum = 0;
        foreach (var b in data)
        {
            checksum ^= b;
        }
        return checksum;
    }

    // Method that modifies data
    public void EncryptData(Span<byte> data, byte key)
    {
        for (int i = 0; i < data.Length; i++)
        {
            data[i] ^= key;
        }
    }

    public void ProcessStream()
    {
        Span<byte> buffer = stackalloc byte[512];
        
        // Fill buffer with data...
        
        // Implicit conversion to ReadOnlySpan<byte>
        var checksum = CalculateChecksum(buffer); // ✅ Works!
        
        // Modify the buffer
        EncryptData(buffer, 0x5A);
        
        // Calculate checksum again
        var newChecksum = CalculateChecksum(buffer); // ✅ Still works!
        
        Console.WriteLine($"Checksums: {checksum} → {newChecksum}");
    }
}

๐ŸŽฏ Performance Benefits

// Stack-allocated buffer for zero-allocation processing
public static bool ValidateData(ReadOnlySpan<char> input)
{
    return input.Length > 0 && input[0] == '{' && input[^1] == '}';
}

// Can now pass strings directly!
string json = "{\"name\":\"John\"}";
if (ValidateData(json)) // Implicit string → ReadOnlySpan<char>
{
    Console.WriteLine("Valid JSON structure");
}

// Stack-allocated buffer also works
Span<char> buffer = stackalloc char[100];
int length = FormatData(buffer);
if (ValidateData(buffer.Slice(0, length))) // Span → ReadOnlySpan
{
    Console.WriteLine("Buffer validated");
}

๐Ÿ’ก Key Benefits:

  • Zero-copy operations — Pass data without allocations or copying
  • Cleaner API design — Methods can accept ReadOnlySpan for immutability guarantees
  • Better performance — Stack-allocated buffers with minimal overhead
  • Improved interop — Easier integration with native code and unsafe contexts
  • Type safety — Compiler enforces read-only vs. mutable semantics

๐Ÿ“‹ Common Patterns

// Pattern 1: Parse without allocation
public static int ParseNumber(ReadOnlySpan<char> text)
{
    return int.Parse(text);
}

string input = "12345";
var number = ParseNumber(input); // No substring allocation!

// Pattern 2: Slice and process
void ProcessSegments(ReadOnlySpan<char> data)
{
    var firstPart = data.Slice(0, 10);
    var secondPart = data.Slice(10);
    // Process without creating new strings
}

// Pattern 3: Safe buffer operations
void SafeReadBuffer()
{
    Span<byte> writeBuffer = new byte[100];
    FillBuffer(writeBuffer);
    
    // Pass as read-only to prevent modifications
    VerifyBuffer(writeBuffer); // Implicit → ReadOnlySpan
}

6. Lambda Parameter Modifiers

๐Ÿ”„ Enhanced Lambda Expressions

C# 14 allows you to use parameter modifiers (ref, in, out) in lambda expressions, enabling more efficient parameter passing and better performance in high-throughput scenarios.

๐Ÿงช Example: Using ref in Lambdas

// Before C# 14 - Had to use delegates explicitly
delegate void RefAction<T>(ref T value);

void ProcessWithRef(RefAction<int> action)
{
    int value = 10;
    action(ref value);
    Console.WriteLine(value);
}

// C# 14 - Use ref directly in lambda
var incrementer = (ref int x) => x++;

int number = 5;
incrementer(ref number);
Console.WriteLine(number); // Output: 6

๐Ÿ’ก Real-World Example: High-Performance Data Processing

public class DataTransformer
{
    // Transform large structs in-place without copying
    public void TransformInPlace<T>(
        List<T> items,
        RefAction<T> transformer) where T : struct
    {
        for (int i = 0; i < items.Count; i++)
        {
            var temp = items[i];
            transformer(ref temp);
            items[i] = temp;
        }
    }
}

// Usage: Transform points without allocation
struct Point
{
    public double X, Y;
}

var points = new List<Point> { new() { X = 1, Y = 2 } };
var transformer = new DataTransformer();

// Lambda with ref modifier - modifies in place
transformer.TransformInPlace(points, (ref Point p) => 
{
    p.X *= 2;
    p.Y *= 2;
});

๐Ÿ“Š Using in for Read-Only References

// Use 'in' to pass large structs efficiently without copying
struct Matrix4x4
{
    public double[,] Data;
    // ... large struct with 16 doubles
}

// Custom delegate type with 'in' parameter
public delegate double InFunc<T>(in T value);

// Lambda with 'in' parameter - read-only, no copy
InFunc<Matrix4x4> calculateDeterminant = 
    (in Matrix4x4 matrix) =>
    {
        // Access matrix without copying the entire struct
        return matrix.Data[0, 0] * matrix.Data[1, 1];
    };

var matrix = new Matrix4x4();
var det = calculateDeterminant(in matrix);

๐ŸŽฏ Using out for Output Parameters

// Custom delegate type with 'out' parameter
public delegate bool TryParseFunc<T>(string input, out T result);

// Lambda that returns multiple values via out parameter
TryParseFunc<int> tryParse = 
    (string input, out int result) =>
    {
        return int.TryParse(input, out result);
    };

if (tryParse("42", out var value))
{
    Console.WriteLine($"Parsed: {value}");
}

// Practical example: Validation with details
public delegate bool TryValidate(string input, out string errorMessage);

TryValidate validateEmail = 
    (string email, out string errorMessage) =>
    {
        if (string.IsNullOrEmpty(email))
        {
            errorMessage = "Email cannot be empty";
            return false;
        }
        if (!email.Contains("@"))
        {
            errorMessage = "Invalid email format";
            return false;
        }
        errorMessage = string.Empty;
        return true;
    };

๐Ÿ’ก Key Benefits:

  • Better performance — Avoid copying large structs
  • More expressive — Write functional code with side effects when needed
  • Cleaner APIs — Use lambdas in more scenarios without wrapper methods
  • Type safety — Compiler enforces parameter semantics

7. Partial Events and Constructors

๐Ÿ“‚ Modular Code Organization

C# 14 extends the partial keyword to events and constructors, allowing you to split their implementation across multiple files. This is especially useful for generated code scenarios and large class implementations.

๐Ÿงช Example: Partial Constructors

// File: Person.cs - Main implementation
public partial class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    // Partial constructor declaration
    public partial Person(string firstName, string lastName);
}

// File: Person.Generated.cs - Auto-generated validation logic
public partial class Person
{
    // Partial constructor implementation
    public partial Person(string firstName, string lastName)
    {
        // Generated validation and initialization
        if (string.IsNullOrWhiteSpace(firstName))
            throw new ArgumentException(nameof(firstName));
        
        if (string.IsNullOrWhiteSpace(lastName))
            throw new ArgumentException(nameof(lastName));
        
        FirstName = firstName;
        LastName = lastName;
        Age = 0;
        
        // Call generated initialization logic
        InitializeGeneratedProperties();
    }

    partial void InitializeGeneratedProperties();
}

๐Ÿ’ก Example: Partial Events

// File: DataService.cs - Main class
public partial class DataService
{
    // Partial event declaration
    public partial event EventHandler<DataChangedEventArgs> DataChanged;

    public void UpdateData(string newData)
    {
        // Business logic
        ProcessData(newData);
        
        // Raise the partial event
        DataChanged?.Invoke(this, new DataChangedEventArgs(newData));
    }

    private void ProcessData(string data) { /* ... */ }
}

// File: DataService.Logging.cs - Logging concerns
public partial class DataService
{
    // Partial event implementation with logging
    public partial event EventHandler<DataChangedEventArgs> DataChanged
    {
        add
        {
            Console.WriteLine($"Subscriber added to DataChanged event");
            field += value; // C# 14 field keyword
        }
        remove
        {
            Console.WriteLine($"Subscriber removed from DataChanged event");
            field -= value;
        }
    }
}

๐ŸŽฏ Real-World Use Case: Separating Generated Code

// File: ViewModel.cs - Hand-written code
public partial class UserViewModel
{
    private string _username;
    
    // Partial event for property changes
    public partial event PropertyChangedEventHandler PropertyChanged;

    public string Username
    {
        get => _username;
        set
        {
            if (_username != value)
            {
                _username = value;
                OnPropertyChanged(nameof(Username));
            }
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

// File: ViewModel.Generated.cs - Source generated code
public partial class UserViewModel
{
    // Generated partial constructor with dependency injection
    public partial UserViewModel(
        ILogger logger,
        IDataService dataService);

    public partial UserViewModel(
        ILogger logger,
        IDataService dataService)
    {
        // Generated initialization
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        _dataService = dataService ?? throw new ArgumentNullException(nameof(dataService));
        
        _logger.LogInformation("UserViewModel created");
    }

    // Generated partial event implementation with weak references
    public partial event PropertyChangedEventHandler PropertyChanged
    {
        add { _weakEventManager.AddHandler(value); }
        remove { _weakEventManager.RemoveHandler(value); }
    }

    private readonly WeakEventManager _weakEventManager = new();
    private readonly ILogger _logger;
    private readonly IDataService _dataService;
}

๐Ÿ’ก Key Benefits:

  • Clean separation — Keep generated code separate from hand-written code
  • Better maintainability — Organize large classes across multiple files
  • Source generators — Perfect for code generation scenarios
  • Team collaboration — Different team members can work on different aspects

8. User-Defined Compound Assignment Operators

๐Ÿ”ง Customize Compound Operations

C# 14 allows you to define custom behavior for compound assignment operators like +=, -=, *=, etc. This enables more intuitive APIs for custom types.

๐Ÿงช Example: Custom Vector Type

public struct Vector3
{
    public double X, Y, Z;

    public Vector3(double x, double y, double z)
    {
        X = x; Y = y; Z = z;
    }

    // Define the fundamental operators
    public static Vector3 operator +(Vector3 a, Vector3 b)
        => new(a.X + b.X, a.Y + b.Y, a.Z + b.Z);

    public static Vector3 operator *(Vector3 v, double scalar)
        => new(v.X * scalar, v.Y * scalar, v.Z * scalar);
}

// Usage — Compound assignments use the defined operators
var velocity = new Vector3(1, 2, 3);
var acceleration = new Vector3(0.1, 0.2, 0.3);

velocity += acceleration; // Uses operator + under the hood
velocity *= 0.5;          // Uses operator * under the hood

๐Ÿ’ก Example: StringBuilder-like Builder Pattern

public class QueryBuilder
{
    private StringBuilder _query = new();

    // Define fundamental operators
    public static QueryBuilder operator +(QueryBuilder builder, string clause)
    {
        if (builder._query.Length > 0)
            builder._query.Append(" ");
        builder._query.Append(clause);
        return builder;
    }

    public static QueryBuilder operator &(QueryBuilder builder, string condition)
    {
        if (!builder._query.ToString().Contains("WHERE"))
            builder._query.Append(" WHERE ");
        else
            builder._query.Append(" AND ");
        builder._query.Append(condition);
        return builder;
    }

    public override string ToString() => _query.ToString();
}

// Usage — Intuitive query building
var query = new QueryBuilder();
query += "SELECT * FROM Users";
query &= "Age > 18";
query &= "IsActive = 1";
query += "ORDER BY Name";

Console.WriteLine(query);
// Output: SELECT * FROM Users WHERE Age > 18 AND IsActive = 1 ORDER BY Name

๐ŸŽฏ Example: Collection Builder with Validation

public class ValidatedCollection<T>
{
    private List<T> _items = new();
    private Func<T, bool> _validator;

    public ValidatedCollection(Func<T, bool> validator)
    {
        _validator = validator;
    }

    // Define fundamental operators
    public static ValidatedCollection<T> operator +(
        ValidatedCollection<T> collection, T item)
    {
        if (collection._validator(item))
        {
            collection._items.Add(item);
            Console.WriteLine($"✓ Added: {item}");
        }
        else
        {
            Console.WriteLine($"✗ Rejected: {item}");
        }
        return collection;
    }

    public static ValidatedCollection<T> operator -(
        ValidatedCollection<T> collection, T item)
    {
        collection._items.Remove(item);
        return collection;
    }

    public int Count => _items.Count;
}

// Usage
var numbers = new ValidatedCollection<int>(x => x > 0);
numbers += 5;   // ✓ Added: 5
numbers += -3;  // ✗ Rejected: -3
numbers += 10;  // ✓ Added: 10

๐Ÿ’ก Key Benefits:

  • Intuitive syntax — Make custom types behave like built-in types
  • Performance — Optimize in-place operations without temporary allocations
  • Fluent APIs — Create chainable, readable code
  • Domain modeling — Express domain concepts naturally

9. Improved Interpolated Strings

๐Ÿ“ Enhanced String Formatting

C# 14 brings significant improvements to string interpolation, including better performance, enhanced formatting options, and support for custom interpolated string handlers.

๐Ÿงช Example: Advanced Formatting

// Enhanced format specifiers
var price = 1234.567;
var date = new DateTime(2025, 11, 3);

// Multiple format specifiers with alignment
var formatted = $"Price: {price,15:C2} | Date: {date:yyyy-MM-dd}";
// Output: "Price:      $1,234.57 | Date: 2025-11-03"

// C# 14: Conditional formatting within interpolation
var stock = 5;
var message = $"Stock: {stock} ({(stock > 0 ? \"Available\" : \"Out of stock\")})";

// Nested interpolations (improved in C# 14)
var items = new[] { "Apple", "Banana", "Cherry" };
var list = $"Items: {string.Join(", ", items.Select(x => $"'{x}'"))}";
// Output: "Items: 'Apple', 'Banana', 'Cherry'"

๐Ÿ’ก Performance: Custom Interpolated String Handlers

// Custom handler for SQL query building with parameter safety
[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
public ref struct SqlInterpolatedStringHandler
{
    private System.Text.StringBuilder _builder;
    private System.Collections.Generic.List<object> _parameters;

    public SqlInterpolatedStringHandler(int literalLength, int formattedCount)
    {
    _builder = new System.Text.StringBuilder(literalLength);
    _parameters = new System.Collections.Generic.List<object>(formattedCount);
    }

    public void AppendLiteral(string s) => _builder.Append(s);

    public void AppendFormatted<T>(T value)
    {
        _parameters.Add(value);
        _builder.Append($"@p{_parameters.Count - 1}");
    }

    public (string Query, object[] Parameters) GetResult()
        => (_builder.ToString(), _parameters.ToArray());
}

// Usage - Safe parameterized queries
public static class SqlQuery
{
    public static (string Query, object[] Parameters) Create(
        SqlInterpolatedStringHandler handler)
    {
        return handler.GetResult();
    }
}

// Create safe SQL queries
var userId = 42;
var userName = "John";

var (query, parameters) = SqlQuery.Create(
    $"SELECT * FROM Users WHERE Id = {userId} AND Name = {userName}");

Console.WriteLine(query);
// Output: "SELECT * FROM Users WHERE Id = @p0 AND Name = @p1"
Console.WriteLine(string.Join(", ", parameters));
// Output: "42, John"

๐ŸŽฏ Example: Logging with Conditional Evaluation

// Custom logger that only evaluates expensive operations if needed
public enum LogLevel { Debug = 1, Info = 2, Warn = 3, Error = 4 }

public class SmartLogger
{
    private LogLevel _currentLevel = LogLevel.Info;

    public void Log(
        LogLevel level,
        [System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute("this", "level")]
        LogInterpolatedStringHandler handler)
    {
        if (level >= _currentLevel)
        {
            Console.WriteLine(handler.ToString());
        }
    }
}

[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
public ref struct LogInterpolatedStringHandler
{
    private System.Text.StringBuilder? _builder;
    private bool _enabled;

    public LogInterpolatedStringHandler(
        int literalLength,
        int formattedCount,
        SmartLogger logger,
        LogLevel level,
        out bool isEnabled)
    {
        _enabled = level >= logger._currentLevel;
        isEnabled = _enabled;
        _builder = _enabled ? new System.Text.StringBuilder(literalLength) : null;
    }

    public void AppendLiteral(string s)
    {
        if (_enabled) _builder!.Append(s);
    }

    public void AppendFormatted<T>(T value)
    {
        if (_enabled) _builder!.Append(value);
    }

    public override string ToString() => _builder?.ToString() ?? string.Empty;
}

// Usage - Expensive operations only run if logging is enabled
var logger = new SmartLogger();
var data = new List<int> { 1, 2, 3 };

string ExpensiveOperation(IEnumerable<int> items)
{
    return string.Join(",", items.Select(i => (i * i).ToString()));
}

// This expensive operation only runs if Debug level is enabled
logger.Log(LogLevel.Debug,
    $"Processing {data.Count} items, details: {ExpensiveOperation(data)}");

๐Ÿ“Š Real-World Example: URL Builder

public class UrlBuilder
{
    public static string Build(UrlInterpolatedStringHandler handler)
        => handler.ToString();
}

[System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute]
public ref struct UrlInterpolatedStringHandler
{
    private System.Text.StringBuilder _builder;

    public UrlInterpolatedStringHandler(int literalLength, int formattedCount)
    {
    _builder = new System.Text.StringBuilder(literalLength);
    }

    public void AppendLiteral(string s) => _builder.Append(s);

    public void AppendFormatted(string value)
    {
        // Automatically URL-encode interpolated values
        _builder.Append(Uri.EscapeDataString(value ?? string.Empty));
    }

    public override string ToString() => _builder.ToString();
}

// Usage - Automatic URL encoding
var searchTerm = "C# programming";
var category = "Books & Media";

var url = UrlBuilder.Build(
    $"https://example.com/search?q={searchTerm}&category={category}");

Console.WriteLine(url);
// Output: "https://example.com/search?q=C%23%20programming&category=Books%20%26%20Media"

๐Ÿ’ก Key Benefits:

  • Zero-allocation — Custom handlers can optimize memory usage
  • Conditional evaluation — Skip expensive operations when not needed
  • Type safety — Enforce formatting rules at compile time
  • Domain-specific — Create specialized string builders (SQL, URLs, etc.)
  • Better performance — Reduced allocations in hot paths

10. Collection Expressions (Preview)

๐Ÿ“ฆ Concise Collection Initialization

C# 14 introduces collection expressions, providing a unified, concise syntax for creating and initializing collections of any type. This feature simplifies code and makes collection creation more intuitive.

๐Ÿงช Example: Basic Collection Expressions

// Traditional way
var oldList = new List<int> { 1, 2, 3, 4, 5 };
var oldArray = new int[] { 1, 2, 3, 4, 5 };

// C# 14: Collection expressions - unified syntax
List<int> list = [1, 2, 3, 4, 5];
int[] array = [1, 2, 3, 4, 5];
Span<int> span = [1, 2, 3, 4, 5];

// Empty collections
List<string> empty = [];
int[] emptyArray = [];

๐Ÿ’ก Example: Spread Operator

// Combine collections with spread operator ..
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
int[] third = [7, 8, 9];

// Spread and combine
int[] combined = [..first, ..second, ..third];
// Result: [1, 2, 3, 4, 5, 6, 7, 8, 9]

// Mix literals and spreads
int[] mixed = [0, ..first, 10, ..second, 20];
// Result: [0, 1, 2, 3, 10, 4, 5, 6, 20]

// Spread with LINQ
var numbers = [1, 2, 3, 4, 5];
var doubled = [..numbers.Select(x => x * 2)];
// Result: [2, 4, 6, 8, 10]

๐ŸŽฏ Example: Pattern Matching with Collections

// Pattern matching with collection expressions
string DescribeCollection(int[] numbers) => numbers switch
{
    [] => "Empty collection",
    [var single] => $"Single element: {single}",
    [var first, var second] => $"Two elements: {first}, {second}",
    [var first, .., var last] => $"First: {first}, Last: {last}",
    _ => $"Collection with {numbers.Length} elements"
};

// Usage
Console.WriteLine(DescribeCollection([]));           // "Empty collection"
Console.WriteLine(DescribeCollection([42]));         // "Single element: 42"
Console.WriteLine(DescribeCollection([1, 2]));       // "Two elements: 1, 2"
Console.WriteLine(DescribeCollection([1, 2, 3, 4])); // "First: 1, Last: 4"

๐Ÿ”ฅ Real-World Example: API Response Building

public record ApiResponse(string Status, object[] Data);

public class UserService
{
    public ApiResponse GetUsers(bool includeAdmin, bool includeGuests)
    {
        var regularUsers = GetRegularUsers();
        var adminUsers = includeAdmin ? GetAdminUsers() : [];
        var guestUsers = includeGuests ? GetGuestUsers() : [];

        // Build response with collection expressions
        return new ApiResponse(
            "success",
            [
                ..regularUsers,
                ..adminUsers,
                ..guestUsers
            ]
        );
    }

    private object[] GetRegularUsers() => [
        new { Id = 1, Name = "Alice", Role = "User" },
        new { Id = 2, Name = "Bob", Role = "User" }
    ];

    private object[] GetAdminUsers() => [
        new { Id = 100, Name = "Admin", Role = "Admin" }
    ];

    private object[] GetGuestUsers() => [
        new { Id = 200, Name = "Guest", Role = "Guest" }
    ];
}

๐Ÿ“Š Example: Building Test Data

// Easy test data creation
public class Product
{
    public string Name { get; set; } = string.Empty;
    public int Price { get; set; }
}

public static class TestDataBuilder
{
    public static List<Product> CreateTestProducts()
    {
        var electronics = CreateElectronics();
        var books = CreateBooks();
        var featured = new Product { Name = "Featured Item", Price = 999 };

        // Combine with collection expressions
        return [featured, ..electronics, ..books];
    }

    private static Product[] CreateElectronics() => [
        new Product { Name = "Laptop", Price = 1200 },
        new Product { Name = "Phone", Price = 800 }
    ];

    private static Product[] CreateBooks() => [
        new Product { Name = "C# Guide", Price = 45 },
        new Product { Name = "Design Patterns", Price = 55 }
    ];
}

// Simple check without external test frameworks
int Sum(int[] numbers) => numbers.Sum();
Console.WriteLine(Sum([1,2,3]));   // 6
Console.WriteLine(Sum([10,20,30])); // 60

Performance Example: Stack Allocation

// Collection expressions work with Span for stack allocation
public static void ProcessData()
{
    // Stack-allocated span with collection expression
    Span<int> numbers = [1, 2, 3, 4, 5];
    
    // Zero heap allocation!
    foreach (var num in numbers)
    {
        Console.WriteLine(num * 2);
    }

    // Combine with stackalloc for larger data
    Span<byte> buffer = stackalloc byte[256];
    Span<byte> header = [0xFF, 0xFE, 0xFD];
    
    // Copy header to buffer
    header.CopyTo(buffer);
}

๐Ÿ’ก Key Benefits:

  • Unified syntax — Same syntax for arrays, lists, spans, and more
  • More readable — Cleaner, less verbose collection initialization
  • Spread operator — Easy collection combination and manipulation
  • Pattern matching — Powerful collection destructuring
  • Performance — Works with stack-allocated spans
  • Type inference — Compiler figures out the target type

๐Ÿš€ Conclusion

C# 14 continues the language's tradition of thoughtful, developer-friendly improvements. From extension members that extend types more naturally, to the field keyword that eliminates boilerplate, these features help you write code that's both more expressive and maintainable.

The addition of lambda parameter modifiers enables more efficient functional programming patterns, while partial events and constructors improve code organization in large projects. User-defined compound assignment operators make custom types feel more natural, and improved interpolated strings provide both performance and flexibility.

Finally, collection expressions bring a modern, unified syntax for working with collections, making code more concise and readable. As you adopt C# 14 in your projects, you'll find these small enhancements add up to significant productivity gains. The language continues to evolve in ways that respect existing code while opening new possibilities for elegant solutions.

๐Ÿ“š Next Steps:

  • Experiment with these features in your own projects
  • Explore the official C# 14 documentation for more details
  • Share your experiences and patterns with the community
  • Stay tuned for more advanced use cases and best practices

Happy coding with C# 14! ๐ŸŽ‰

Tuesday, 8 April 2025

How to add a right-click context menu option in Windows Explorer

 To add a right-click context menu option in Windows Explorer that opens the current folder in Command Prompt and automatically runs code . (to open it in VS Code), you can do this by editing the Windows Registry.

๐Ÿš€ What This Will Do

When you right-click on a folder (or inside a folder and right-click empty space), you'll see an option like "Open with VS Code (code .)", which:

  1. Opens a Command Prompt in that folder.

  2. Runs code . automatically.

๐Ÿ› ️ Steps to Add the Context Menu Option

✅ Step 1: Open Registry Editor

        Press Win + R, type regedit, and press Enter.

✅ Step 2: Navigate to the Context Menu Key

Go to:

Computer\HKEY_CLASSES_ROOT\Directory\Background\shell

Tip: This location adds the context menu item when you right-click inside a folder (not on a folder).

✅ Step 3: Create a New Key

  1. Right-click on shell → New → Key → name it:

    Open VSCode Here
  2. Select this new key and in the right pane, double-click on (Default) and set the value to:

    Open with VS Code (code .)

✅ Step 4: Add the Command

  1. Right-click on the Open VSCode Here key → New → Key → name it:

    command
  2. Select the command key, and in the right pane, se

    t the (Default) value to:

    cmd.exe /k "code ."

๐Ÿงช Test It

  • Right-click inside any folder in Windows Explorer.

  • You should see: "Open with VS Code (code .)"

  • Clicking it opens Command Prompt and runs code .

Saturday, 28 December 2024

How to use User-secret to protect secrets in dotnet application.

 

 Using user secrets in a .NET Web API project to securely manage your database password is an excellent practice. It keeps sensitive information like database passwords out of your source code and version control system.  

    Questions about managing secrets and securely handling sensitive data in .NET applications are quite common in job interviews, especially for roles that require knowledge of security best practices. Understanding how to securely manage and deploy secrets demonstrates your ability to maintain the security and integrity of an application.

Why Secrets Management is Important:

  • Protects sensitive information like API keys, database passwords, and other confidential data.

  • Prevents unauthorized access and potential data breaches.

  • Protects from uploading such sensitive information in code repository.

User Secrets in Development:

  • Use Microsoft.Extensions.Configuration.UserSecrets to store secrets securely during development.

  • Initialize user secrets in your project: 

 dotnet user-secrets init --project [projectName]

  •  Set secrets using the CLI:
     
  • dotnet user-secrets --project [projectName]
      set "ConnectionStrings:DefaultConnection:Password" "YourDatabasePassword"
Accessing Secrets in Code:
  • Load user secrets in your Program.cs or Startup.cs.
    var builder = WebApplication.CreateBuilder(args);
    builder.Configuration.AddUserSecrets<Program>();


Example appsettings.json:
{
    "ConnectionStrings": {
      "DefaultConnection": "Server=your_server;Database=your_database;User Id=your_userid;TrustServerCertificate=True;MultipleActiveResultSets=true;"
    }
  }
Program.cs or startup.cs
var builder = WebApplication.CreateBuilder(args);

// Load User Secrets
builder.Configuration.AddUserSecrets<Program>();

// Build the connection string
var defaultConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var dbPassword = builder.Configuration["ConnectionStrings:DefaultConnection:Password"];
var connectionString = $"{defaultConnectionString}Password={dbPassword};";

// Add services to the container.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));

var app = builder.Build();

Best Practices for Production:
  • Use environment variables or a secret management service (e.g., Azure Key Vault, AWS Secrets Manager) to store and retrieve secrets in production.
  • Configure your application to load secrets from the chosen service:
        
builder.Configuration.AddEnvironmentVariables();
// Or use Azure Key Vault
builder.Configuration.AddAzureKeyVault(
    new Uri("https://your-keyvault-name.vault.azure.net/"),
new DefaultAzureCredential());

Automating Secret Management:
  • Integrate secret management into CI/CD pipelines to automate the secure deployment of secrets.
  • Use tools like Azure DevOps, GitHub Actions, or Jenkins to manage secrets during deployment.

    Example Scenario:

    Question: How would you securely manage and deploy secrets in a .NET Web API application?
    Answer: "In a .NET Web API application, I would use Microsoft.Extensions.Configuration.UserSecrets to manage secrets during development. This helps keep sensitive information like database passwords secure and out of source control. For production environments, I would leverage a secret management service like Azure Key Vault to store and retrieve secrets securely. This ensures that secrets are managed centrally and securely accessed by the application. Additionally, I would integrate secret management into CI/CD pipelines to automate the secure deployment of secrets, ensuring that sensitive information remains protected throughout the development and deployment process."

Monday, 2 December 2024

Create a Directory from existing disk in proxmox server

Scenario:
 I have an NVMe disk with an ext4 partition that was previously used as a directory in a Proxmox server. After reinstalling Proxmox, I’m unable to add the drive as a directory because the disk selection page shows the message "No disks unused." I need to retain the data on the drive since it contains backups of all the VMs and cannot be formatted.



The issue occurs because Proxmox VE's interface doesn't automatically recognize pre-used disks with existing filesystems as "unused." To safely add the drive back as a directory without losing the data, follow these steps:

Steps to Add the Existing Disk

  1. Verify the Filesystem and Mount the Disk

    • SSH into your Proxmox server.

    • Identify your NVMe drive and its partition using:


      lsblk

      or

      fdisk -l

      Look for the partition (e.g., /dev/nvme0n1p1).

    • Check the filesystem to ensure it's still intact:


      file -s /dev/nvme0n1p1

      If it shows as an ext4 filesystem, you're good to proceed.

    • Mount the partition to a temporary directory to confirm its contents:


      mkdir /mnt/temp mount /dev/nvme0n1p1 /mnt/temp ls /mnt/temp

      Ensure you see the backup files.



  2. Create a Permanent Mount Point

    • Decide where you want to mount the drive. For example:


      mkdir /mnt/nvme-backups
    • Edit /etc/fstab to automatically mount the partition on boot:


      nano /etc/fstab

      Add an entry similar to this:


      /dev/nvme0n1p1 /mnt/nvme-backups ext4 defaults 0 2

      Replace /dev/nvme0n1p1 with your actual device path.


    • Mount the disk:


      mount -a

       


  3. Add the Directory to Proxmox Storage

    • Go to the Proxmox web interface.
    • Navigate to Datacenter > Storage > Add > Directory.
    • In the "Directory" field, input the mount point path (e.g., /mnt/nvme-backups).
    • Select the desired content types (e.
      g., VZDump backup file for backups).
  4. Test the Setup

    • Check if the backups are accessible in Proxmox.
    • Ensure the directory is listed in Datacenter > Storage and shows the correct size and usage.

Sunday, 1 December 2024

Change static Ip of a Xcp-ng server



SSH to the server . 

sudo -s

type password for xoa.  

$ xoa network static

? Static IP for this machine 192.168.0.10/24
? Gateway 192.168.0.1
? IP of the DNS server 8.8.8.8




Saturday, 2 November 2024

Create windows vm in Azure using terraform




Creating a Windows VM in Azure using Terraform is a great way to manage your infrastructure as code. Here's a step-by-step guide to help you get started:

Step 1: Prerequisites

  1. Azure Subscription: Ensure you have an active Azure subscription.

  2. Terraform: Install Terraform on your local machine.

  3. Azure CLI: Install and configure the Azure CLI.

Step 2: Create Terraform Configuration Files

Create a directory for your Terraform configuration files and navigate to it:

sh
mkdir terraform-windows-vm
cd terraform-windows-vm

Create a main.tf file and add the following Terraform code:

hcl
# Provider configuration
provider "azurerm" {
  features {}
}

# Resource group
resource "azurerm_resource_group" "rg" {
  name     = "terraform-windows-vm-rg"
  location = "East US"
}

# Virtual network
resource "azurerm_virtual_network" "vnet" {
  name                = "terraform-vnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
}

# Subnet
resource "azurerm_subnet" "subnet" {
  name                 = "terraform-subnet"
  address_prefix       = "10.0.1.0/24"
  virtual_network_name = azurerm_virtual_network.vnet.name
  resource_group_name  = azurerm_resource_group.rg.name
}

# Public IP
resource "azurerm_public_ip" "public_ip" {
  name                = "terraform-public-ip"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  allocation_method   = "Dynamic"
}

# Network interface
resource "azurerm_network_interface" "nic" {
  name                = "terraform-nic"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
  }

  ip_configuration {
    name                          = "public"
    subnet_id                     = azurerm_subnet.subnet.id
    public_ip_address_id          = azurerm_public_ip.public_ip.id
  }
}

# Storage account
resource "azurerm_storage_account" "storage" {
  name                     = "terraformstorage"
  location                 = azurerm_resource_group.rg.location
  resource_group_name      = azurerm_resource_group.rg.name
  account_tier             = "Standard"
  account_replication_type = "LRS"
}

# Virtual machine
resource "azurerm_windows_virtual_machine" "vm" {
  name                  = "terraform-vm"
  location              = azurerm_resource_group.rg.location
  resource_group_name   = azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.nic.id]
  vm_size               = "Standard_D2_v4"
  storage_account_name  = azurerm_storage_account.storage.name
  storage_account_key   = azurerm_storage_account.storage.primary_access_key
  os_disk_size_gb       = 30
  os_disk_caching       = "ReadWrite"
  admin_username        = "adminuser"
  admin_password        = "Password1234!"
  computer_name         = "terraform-vm"
}

Step 3: Initialize Terraform

Run the following command to initialize Terraform and download the required providers:

sh
terraform init

Step 4: Create Execution Plan

Run the following command to create an execution plan:

sh
terraform plan

Step 5: Apply the Execution Plan

Run the following command to apply the execution plan and create the resources in Azure:

sh
terraform apply

Step 6: Verify the Results

Once the resources are created, you can verify the results by checking the Azure portal or using Azure CLI commands.




Sunday, 1 September 2024

Task and Thread in c#

 





In C#, threads and tasks are both used for asynchronous programming and parallel execution, but they serve different purposes and provide different levels of abstraction and control. Let's explore the differences between them:

Threads

  1. Low-Level Concept:

    • A thread is the basic unit of execution within a process.

    • It represents a separate path of execution in the application.

  2. Creation:

    • Threads can be created and managed using the System.Threading.Thread class.

    • Example:

      csharp
      using System;
      using System.Threading;
      
      class Program
      {
          static void Main()
          {
              Thread thread = new Thread(new ThreadStart(DoWork));
              thread.Start();
          }
      
          static void DoWork()
          {
              Console.WriteLine("Work on a separate thread");
          }
      }
      
  3. Control:

    • You have fine-grained control over the thread’s lifecycle (e.g., start, sleep, join, abort).

    • Example:

      csharp
      thread.Join(); // Wait for the thread to finish
      
      
  4. State Management:

    • Requires manual state management and synchronization (e.g., using locks, mutexes).

Tasks

  1. High-Level Abstraction:

    • A task is a higher-level abstraction provided by the Task Parallel Library (TPL) in the System.Threading.Tasks namespace.

    • It represents an asynchronous operation and is designed to simplify writing concurrent code.

  2. Creation:

    • Tasks can be created using the Task class.

    • Example:

      csharp
      using System;
      using System.Threading.Tasks;
      
      class Program
      {
          static async Task Main()
          {
              Task task = Task.Run(() => DoWork());
              await task;
          }
      
          static void DoWork()
          {
              Console.WriteLine("Work on a separate task");
          }
      }
      
  3. Control:

    • Tasks are easier to manage, with built-in support for continuations and cancellation.

    • Example:

      csharp
      Task task = Task.Run(() => DoWork());
      task.Wait(); // Wait for the task to finish

  4. State Management:

    • The TPL provides built-in mechanisms for state management and synchronization, reducing the complexity of concurrent programming.

Key Differences

FeatureThreadTask
Abstraction LevelLow-levelHigh-level
NamespaceSystem.ThreadingSystem.Threading.Tasks
Creationnew Thread(...)Task.Run(...), Task.Factory.StartNew(...)
ControlStart, Sleep, Join, AbortWait, ContinueWith, CancellationToken
State ManagementManual synchronization requiredBuilt-in support for synchronization and continuations
Use CaseFine-grained control neededSimplified asynchronous programming, parallelism

Summary

  • Threads: Lower-level, more control, requires manual synchronization, used for precise thread management.

  • Tasks: Higher-level abstraction, easier to use, built-in support for synchronization and continuation, ideal for parallelism and asynchronous programming.

Using tasks is generally recommended for modern C# programming because they are easier to manage and provide more features for handling asynchronous operations efficiently. 

Thursday, 1 August 2024

Kubernetes easy installation guide

 Install Kubernetes guide:



After lots of research, I've found a easy to follow tutorial to install Kubernetes cluster. Here's the link to the video: 

https://www.youtube.com/watch?v=I9goyp8mWfs

https://www.itsgeekhead.com/tuts/kubernetes-129-ubuntu-22-04-3/



UBUNTU SERVER LTS 24.04.0 - https://ubuntu.com/download/server

KUBERNETES 1.30.1         - https://kubernetes.io/releases/

CONTAINERD 1.7.18         - https://containerd.io/releases/

RUNC 1.2.0-rc.1               - https://github.com/opencontainers/runc/releases

CNI PLUGINS 1.5.0         - https://github.com/containernetworking/plugins/releases

CALICO CNI 3.28.0         - https://docs.tigera.io/calico/3.27/getting-started/kubernetes/quickstart


3 NODES, 2 vCPU, 8 GB RAM, 50GB Disk EACH

k8s-control   10.10.10.2

k8s-01         10.10.10.3

k8s-02         10.10.10.4




### ALL:


sudo su


printf "\n10.10.10.2 k8s-control\n10.10.10.3 k8s-1\n10.10.10.4 k8s-1\n\n" >> /etc/hosts


printf "overlay\nbr_netfilter\n" >> /etc/modules-load.d/containerd.conf


modprobe overlay

modprobe br_netfilter


printf "net.bridge.bridge-nf-call-iptables = 1\nnet.ipv4.ip_forward = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\n" >> /etc/sysctl.d/99-kubernetes-cri.conf


sysctl --system


wget https://github.com/containerd/containerd/releases/download/v1.7.18/containerd-1.7.18-linux-amd64.tar.gz -P /tmp/

tar Cxzvf /usr/local /tmp/containerd-1.7.18-linux-amd64.tar.gz

wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -P /etc/systemd/system/

systemctl daemon-reload

systemctl enable --now containerd


wget https://github.com/opencontainers/runc/releases/download/v1.2.0-rc.1/runc.amd64 /tmp/

install -m 755 /tmp/runc.amd64 /usr/local/sbin/runc


wget https://github.com/containernetworking/plugins/releases/download/v1.5.0/cni-plugins-linux-amd64-v1.5.0.tgz -P /tmp/

mkdir -p /opt/cni/bin

tar Cxzvf /opt/cni/bin /tmp/cni-plugins-linux-amd64-v1.5.0.tgz


mkdir -p /etc/containerd

containerd config default | tee /etc/containerd/config.toml   <<<<<<<<<<< manually edit and change SystemdCgroup to true (not systemd_cgroup)

vi /etc/containerd/config.toml

systemctl restart containerd



swapoff -a  <<<<<<<< just disable it in /etc/fstab instead


apt-get update

apt-get install -y apt-transport-https ca-certificates curl gpg


mkdir -p -m 755 /etc/apt/keyrings

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list



apt-get update


reboot


sudo su


  apt-get update

  apt-get install -y kubelet kubeadm kubectl

  apt-mark hold kubelet kubeadm kubectl


# check swap config, ensure swap is 0

free -m



### ONLY ON CONTROL NODE .. control plane install:

                kubeadm init --pod-network-cidr 10.10.0.0/16 --kubernetes-version 1.30.1 --node-name k8s-control


                export KUBECONFIG=/etc/kubernetes/admin.conf


                # add Calico 3.28.0 CNI

                kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/tigera-operator.yaml

                wget https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/custom-resources.yaml

                vi custom-resources.yaml <<<<<< edit the CIDR for pods if its custom

                kubectl apply -f custom-resources.yaml


                # get worker node commands to run to join additional nodes into cluster

                kubeadm token create --print-join-command

                ###



### ONLY ON WORKER nodes

Run the command from the token create output above

A Complete Beginner's Guide to Understanding Computer Hardware

What is Hardware? - A Complete Beginner's Guide What is H...