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.
No comments:
Post a Comment