Tuesday, 9 December 2025

Enable font ligatures in Visual Studio 2026

Font ligatures are a cognitive boost for developers when reading code inside an IDE.

What are font ligatures

Font ligatures are special glyphs that combine multiple characters into a single, elegant symbol. For example, =>, ===, or != can appear as smooth connected symbols instead of separate characters. They don’t change your code—just make it more readable and visually appealing.

  • 🎨 Aesthetic boost – Makes your code look clean and modern without changing functionality.
  • πŸ‘️ Better readability – Reduces visual clutter, making code easier on the eyes.
  • πŸ” Clearer syntax – Turns multi-character operators like => or === into neat symbols for quick recognition.
  • Faster comprehension – Helps spot patterns and logic flow at a glance.


In case you want to enable Font ligatures inside VS 2026, Visual Studio 2026, you actually have to resort to running a Powershell script or similar to alter the registry a bit.

EnableFonLigatures.ps1 | Powershell





# Enable Font Ligatures for Visual 2026 (18.x)
$basePath = "HKCU:\Software\Microsoft\VisualStudio"
$targetPrefixes = @("18.0_")
foreach ($prefix in $targetPrefixes) {
    $vsKeys = Get-ChildItem -Path $basePath | Where-Object { $_.PSChildName -like "$prefix*" }
    if ($vsKeys.Count -eq 0) {
        Write-Host "No keys found for prefix $prefix. Open Visual Studio and change Fonts & Colors once, then rerun."
    } else {
        foreach ($key in $vsKeys) {
            $fontColorsPath = Join-Path $key.PSPath "FontAndColors\Text Editor"
            
            # Create the path if missing
            if (-not (Test-Path $fontColorsPath)) {
                Write-Host "Creating missing path: $fontColorsPath"
                New-Item -Path $fontColorsPath -Force | Out-Null
            }
            # Set EnableFontLigatures to 1
            Set-ItemProperty -Path $fontColorsPath -Name "EnableFontLigatures" -Value 1 -Type DWord
            Write-Host "Ligatures enabled for: $fontColorsPath"
        }
    }
}


The following screenshot shows two ligatures symbols. Note the special symbols for => ('goes to') and != ('not equals') that are combined into one elegant symbol, which is more readable for the reader.

Monday, 24 November 2025

Exploring Extension Blocks and Constants in C# 14

Extension blocks - Extension properties

Extension blocks and an important new feature - extension properties can be made in C#14. This is available with .NET 10.

It is not possible to define a generic extension block to add extension properties / members (yet, as of C#14 anyways -maybe for future version of C#..)

Consider this example of some well-known constants from entry-level Calculus using extension properties.


using System.Numerics;

namespace Csharp14NewFeatures
{
    /// <summary>
    /// Provides well-known mathematical constants for any numeric type using generic math.
    /// </summary>
    /// <typeparam name="T">A numeric type implementing INumber<T> (e.g., double, decimal, float).</typeparam>
    public static class MathConstants<T> where T : INumber<T>
    {
        /// <summary>Ο€ (Pi), ratio of a circle's circumference to its diameter.</summary>
        public static T Pi => T.CreateChecked(Math.PI);

        /// <summary>Ο„ (Tau), equal to 2Ο€. Represents one full turn in radians.</summary>
        public static T Tau => T.CreateChecked(2 * Math.PI);

        /// <summary>e (Euler's number), base of the natural logarithm.</summary>
        public static T E => T.CreateChecked(Math.E);

        /// <summary>Ο† (Phi), the golden ratio (1 + √5) / 2.</summary>
        public static T Phi => T.CreateChecked((1 + Math.Sqrt(5)) / 2);

        /// <summary>√2, square root of 2. Appears in geometry and trigonometry.</summary>
        public static T Sqrt2 => T.CreateChecked(Math.Sqrt(2));

        /// <summary>√3, square root of 3. Common in triangle geometry.</summary>
        public static T Sqrt3 => T.CreateChecked(Math.Sqrt(3));

        /// <summary>ln(2), natural logarithm of 2.</summary>
        public static T Ln2 => T.CreateChecked(Math.Log(2));

        /// <summary>ln(10), natural logarithm of 10.</summary>
        public static T Ln10 => T.CreateChecked(Math.Log(10));

        /// <summary>Degrees-to-radians conversion factor (Ο€ / 180).</summary>
        public static T Deg2Rad => T.CreateChecked(Math.PI / 180.0);

        /// <summary>Radians-to-degrees conversion factor (180 / Ο€).</summary>
        public static T Rad2Deg => T.CreateChecked(180.0 / Math.PI);
    }

    /// <summary>
    /// Extension blocks exposing math constants as properties for common numeric types.
    /// </summary>
    public static class MathExtensions
    {
        extension(double source)
        {
            /// <inheritdoc cref="MathConstants{T}.Pi"/>
            public double Pi => MathConstants<double>.Pi;
            public double Tau => MathConstants<double>.Tau;
            public double E => MathConstants<double>.E;
            public double Phi => MathConstants<double>.Phi;
            public double Sqrt2 => MathConstants<double>.Sqrt2;
            public double Sqrt3 => MathConstants<double>.Sqrt3;
            public double Ln2 => MathConstants<double>.Ln2;
            public double Ln10 => MathConstants<double>.Ln10;
            public double Deg2Rad => MathConstants<double>.Deg2Rad;
            public double Rad2Deg => MathConstants<double>.Rad2Deg;
        }

        extension(decimal source)
        {
            public decimal Pi => MathConstants<decimal>.Pi;
            public decimal Tau => MathConstants<decimal>.Tau;
            public decimal E => MathConstants<decimal>.E;
            public decimal Phi => MathConstants<decimal>.Phi;
            public decimal Sqrt2 => MathConstants<decimal>.Sqrt2;
            public decimal Sqrt3 => MathConstants<decimal>.Sqrt3;
            public decimal Ln2 => MathConstants<decimal>.Ln2;
            public decimal Ln10 => MathConstants<decimal>.Ln10;
            public decimal Deg2Rad => MathConstants<decimal>.Deg2Rad;
            public decimal Rad2Deg => MathConstants<decimal>.Rad2Deg;
        }

        extension(float source)
        {
            public float Pi => MathConstants<float>.Pi;
            public float Tau => MathConstants<float>.Tau;
            public float E => MathConstants<float>.E;
            public float Phi => MathConstants<float>.Phi;
            public float Sqrt2 => MathConstants<float>.Sqrt2;
            public float Sqrt3 => MathConstants<float>.Sqrt3;
            public float Ln2 => MathConstants<float>.Ln2;
            public float Ln10 => MathConstants<float>.Ln10;
            public float Deg2Rad => MathConstants<float>.Deg2Rad;
            public float Rad2Deg => MathConstants<float>.Rad2Deg;
        }
    }
}

We must define extension blocks per type here.

If we move over to extension methods, we still must use a non-generic class. However, we can use for example generic math, show below. This allows to reuse code accross multiple types, supporting INumber<T> in this case.


namespace Csharp14NewFeatures
{
    using System;
    using System.Numerics;

    namespace Csharp14NewFeatures
    {
        /// <summary>
        /// Provides generic mathematical constants via extension methods for numeric types.
        /// </summary>
        public static class MathConstantExtensions
        {
            public static T GetPi<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(Math.PI);

            public static T GetTau<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(2 * Math.PI);

            public static T GetEuler<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(Math.E);

            public static T GetPhi<T>(this T _) where T : INumber<T> =>
                T.CreateChecked((1 + Math.Sqrt(5)) / 2);

            public static T GetSqrt2<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(Math.Sqrt(2));

            public static T GetSqrt3<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(Math.Sqrt(3));

            public static T GetLn2<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(Math.Log(2));

            public static T GetLn10<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(Math.Log(10));

            public static T GetDeg2Rad<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(Math.PI / 180.0);

            public static T GetRad2Deg<T>(this T _) where T : INumber<T> =>
                T.CreateChecked(180.0 / Math.PI);
        }
    }
}

Example usage of the code above :


  #region Extension metmbers using block syntax - Math

  //Extension properties 
  double radians = double.Pi / 3.0; // Pi/3 radians = 60 degrees (1 * Pi = 180 degrees) 
  double degrees = radians * radians.Rad2Deg; // Using the extension method Rad2Deg

  Console.WriteLine($"Radians: {radians:F6}"); //outputs 1.04719..
  Console.WriteLine($"Degrees: {degrees:F6}"); //outputs 60

  //Using Extension methods 

    //Using Extension methods 

  double radiansV2 = 1.0.GetPi() / 3.0;
  double degreesV2 = radians * 1.0.GetRad2Deg();

  Console.WriteLine($"Radians: {radiansV2:F6}");
  Console.WriteLine($"Degrees: {degreesV2:F6}");

Output of the code usage above:


Radians: 1,047198
Degrees: 60,000000
Radians: 1,047198
Degrees: 60,000000

So to sum up, if you use extension blocks in C#, you can use them together with generics, but the extension block must be defined to a concrete type, not using generics. This will result in some cases in lengthier code, as we cannot use generics as much as extension methods allows. Note that extension methods also must be defined inside a non-generic class.

Saturday, 22 November 2025

C# 14 - Null-conditional assignments

What's new in C#

With .NET 10 released in November 2025, new features of C# is available.

Null-conditional assignment

In C# 14, Null-conditional assignment allows using the null-conditional member access operator on the left and side of assignment.

This allows more compact code, but also at the same time allow the code to become a bit more indeterministic since the code will not be run if the object on the left side of the assignment is null.

Consider this simple class :


public class AnotherClass
{
  public ShipmentService ShipmentService = new ShipmentService();
  public Order? CurrentOrder { get; set; }  
  public int? Counter { get; set; } = 0;   
}


public class ShipmentService
{
    public Order? GetCurrentOrder()
    {
        // Simulate fetching the current order, which may return null
        return null; // or return new Order { OrderId = 1234 };
    }
}

public class Order
{
    public int OrderId { get; set; }
}

We do a null check on the instance of <em>AnotherClass</em> ShipmentService here on the left side.


//Demonstrate AnotherClass using null-conditional assignment 
AnotherClass? anotherClass = null;
anotherClass?.CurrentOrder = anotherClass?.ShipmentService.GetCurrentOrder();
Console.WriteLine($"Current order retrieved using null-conditional assignment: {anotherClass?.CurrentOrder?.OrderId} Current order id is NULL? {anotherClass?.CurrentOrder?.OrderId is null}");

It is also possible to use the null check of the null-conditional assignment with compound assignment operators. The compound operators are += and -= . Note that this must be done with member access and cannot be used with values such as integers for example.


//anotherClass still NULL
anotherClass?.Counter += 2;
Console.WriteLine($"anotherClass.Counter = {anotherClass?.Counter}. Is anotherClass.Counter NULL ? {anotherClass?.Counter is null} : outputs NULL since anotherClass is still null");
anotherClass = new AnotherClass();
anotherClass?.Counter -= 15;
Console.WriteLine($"anotherClass.Counter = {anotherClass?.Counter} : outputs -15 since anotherClass is not null"); 
Output of the code above:

Current order retrieved using null-conditional assignment:  Current order id is NULL? True
anotherClass.Counter = . Is anotherClass.Counter NULL ? True : outputs NULL since anotherClass is still null
anotherClass.Counter = -15 : outputs -15 since anotherClass is not null