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

Saturday, 1 November 2025

Metadata retrieval and debugging MCP servers

This article will show how one can retrieve metadata and debug MCP servers. We will obtain two kinds of information:
  • Display Json-Rpc formatted metadata of the MCP server. This will be callable via Swagger, contacting a Web Api controller.
  • Browse documentation and use the tools of the MCP server via a freely available tool called ModelInspector.
In this case, I will use the previous article's demo of a Weather client that connects to a server running with MCP. The Github repo for my DEMO MCP server is available here :

https://github.com/toreaurstadboss/WeatherMCPDemo

Let's first see now we can set up Swagger and set up a MVC Web Api controller to expose metadata about the MCP server.

Exposing metadata about the MCP server


Inside Program.cs of our project WeatherServer.Web.Http, we set up Swagger like this:
Program.cs


   public static void Main(string[] args)
   {
       var builder = WebApplication.CreateBuilder(args);
       
       //more code

       //Add swagger support
       builder.Services.AddControllers();
       builder.Services.AddEndpointsApiExplorer();
       builder.Services.AddSwaggerGen();
       
       // some more code
       
       app.UseSwagger();
       app.UseSwaggerUI();


Swagger and its UI is provided via Nuget packages :
WeatherServer.Web.Http.csproj

	<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
	<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.6" />

Next up, let's see how the metadata about the MCP server can be exposed via an MVC controller.
ToolsController.cs


using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Client;

namespace WeatherServer.Web.Http.Controllers
{

    [ApiController]
    [Route("[controller]")]
    public class ToolsController : ControllerBase
    {
        private readonly IMcpClient _client;
        private readonly IOptions<ModelContextProtocol.Protocol.Implementation> _mcpServerOptions;

        public ToolsController(IMcpClient client, IOptions<ModelContextProtocol.Protocol.Implementation> mcpServerOptions)
        {
            _client = client;
            _mcpServerOptions = mcpServerOptions; 
        }

        [HttpGet(Name = "Overview")]
        [Produces("application/json")]
        public async Task<IActionResult> GetOverview()
        {
            var rpcRequest = new
            {
                jsonrpc = "2.0",
                method = "tools/list",
                id = 1
            };

            var tools = await _client.ListToolsAsync();
            //var prompts = await _client.ListPromptsAsync();
            //var resources = await _client.ListResourcesAsync();
            return Ok(new
            {
                ServerName = _mcpServerOptions.Value.Title,
                Version = _mcpServerOptions.Value.Version,
                Tools = tools,
                //Prompts = prompts,
                // Resources = resources
            });
        }

    }

}


We will list the tools on the MCP server. In our server, we have added connection ability to the MCP server itself to achiveve this. Since my MCP demo server only got some tools and has not added prompts or resources, only this will be exposed. Please note that an anonymous object is created with

  jsonrpc = "2.0",
  method = "tools/list"
  id = 1

These "magic" fields with their values instructs MCP to deliver Json RCP data.

Getting the Json-Rpc metadata

The Json-Rpc metadata is then easily obtained from Swagger UI. The following screenshot shows this.

Download the entire .json document. You can use an online Json browser to browse through the Json document. For example the web site JsonCrack offers a powerful tool to browse larger Json document. Url to JsonCrack:

https://jsoncrack.com/editor

Screenshot showing it displaying Json-Rpc document in JsonCrack : Next up, let's see how ModelInspector can be used to discover metadata about the MCP server. Node must be installed on the PC, at least Node version that supports modern ES modules and fetch API. I use Node version 25 when I tested this. Use npx (Node must be installed and setup with PATH environment variable) to have npx available.

  npx @modelcontextprotocol/inspector --startup-url "https://localhost:7145/mcp"

In case you get SSL troubles and want to bypass SSL security in case you for example rely on self-signed certificates and browser are giving you difficulties, you can use the following in LocalDEV from commandline:

  $env:NODE_TLS_REJECT_UNAUTHORIZED=0
  npx @modelcontextprotocol/inspector --startup-url "https://localhost:7145/mcp"

This will temporarily skip TLS errors and allow you to bypass troubles with self-signed certificates. In production environments, you of course will not use this 'trick'. Once inside ModelContextInspector, choose - Transport Type set to : Streamable HTTP - URL set to: https://localhost:7145/sse - Connection Type set to: Via Proxy Once ready, enter the button Connect : Connect It should say Connected with a green diode indicator. Note that Program.cs sets up the MCP endpoint to SSE :

    app.MapMcp("/sse"); // This exposes the SSE endpoint at /sse

Screenshot showing the tool in use : Hit the button List Tools to list the tools in the MCP demo. You will get the description of each tool and by selecting a tool, you can provide its input parameters and also see Description / Instruction usage. You can then invoke the tool by running the button 'Run Tool' and also see additional information such as description of the tool and also the description of each method. This is even more readable than the raw Json-Rpc document we downloaded using Swagger described above. For more advanced MCP servers, you can inspect additional information from Resources, Prompts and additional information. This makes it possible to debug your MCP servers without having to test via a client or chat via for example a Swagger endpoint. The following Nuget packages are used for the server project of the DEMO.


	<PackageReference Include="ModelContextProtocol.AspNetCore" Version="0.3.0-preview.2" /<
	<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
	<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.6" />
	<PackageReference Include="ModelContextProtocol" Version="0.3.0-preview.2" />
	<PackageReference Include="Anthropic.SDK" Version="5.5.1" />
	<PackageReference Include="Microsoft.Extensions.AI" Version="9.9.0" />
	<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-preview.6.25358.103" />


To sum up, to inspect your MCP server in greater detail and if you use technology similar to the Nugets shown here, you can:
  • Add Swagger and Swagger UI to the serverside and expose an endpoint that lists the Json-Rpc metadata for the MCP server such as listing the tools of the MCP server. Or Prompts and Resources, if they have been added to the MCP server.
  • Use the free tool Model Inspector to inspect the MCP server and invoke for example the tools of the MCP server.