C# 11.0: Generic Math, C# Operators and Static Abstract/Virtual Interface Members

In the previous blog posts you learned about different C# 11.0 features:

In this blog post, let’s look at another interesting feature of C# 11.0 which is called Generic Math.

First, let’s take a look at generic math before C# 11.0

Generic Math before C# 11.0

Generic math before C# 11.0 did not exist, but there were always a few hacks around. Let’s say you need a simple Add method to add two integers, and you also want to use it with doubles. To do this, you can overload the Add method by using different data types like you see it in the Additions class below. It has an Add method that works with int values, and another one that works with double values.

public static class Additions
{
    public static int Add(int x, int y)
    {
        return x + y;
    }

    public static double Add(double x, double y)
    {
        return x + y;
    }
}

Now, if you want to support for example the type float as well, you need to adjust the Additions class by adding another Add method that works with the float type. So, for every new type you need to adjust the Additions class. That means not only work, it can also be frustrating if you build a library where consumers of that library cannot add an overload on their own.

Instead of adding a new overload for every new type, you could try to create a generic Add method like you see it in the code snippet below. That generic method should work for every data type, right?

public static class Additions
{
    public static T Add<T>(T x, T y)
    {
        return x + y;
    }
}

When you define a generic method like you see it in the code snippet above, you will get an error that the + operator cannot be applied to the operands of type T and T. I’ve created the screen shot below from Visual Studio. I put the cursor exactly over the + operator to see the error.

But now the interesting part is that even before C# 11.0, there were some workarounds. For example, you could use the dynamic type. In the code snippet below, the values of x and y are stored in the dynamic variables num1 and num2. Now this code compiles, as the + operator is not checked at compile time anymore, as we’re adding two dynamic variables.

public static class Additions
{
    public static T Add<T>(T x, T y)
    {
        dynamic num1 = x;
        dynamic num2 = y;
        return (T)num1 + num2;
    }
}

This means now that you can use the generic Add method with any number type. In the code snippet below you see how it is used for example with the types int and double.

int intResult = Additions.Add<int>(3, 7);
double doubleResult = Additions.Add<double>(1.2, 2.4);

Of course, you can also omit the generic type argument. The C# compiler will infer the type from the method arguments. In the code snippet below, the first Add method call returns an int, as integers are passed to the method as arguments. The second Add method call returns a double, as doubles are passed as arguments to the Add method.

int intResult = Additions.Add(3, 7);
double doubleResult = Additions.Add(1.2, 2.4);

This solution with the dynamic keyword works, as the + operator is not checked at compile time. If it is there at runtime, like for int and double, then everything is fine. But if it is not there, you’ll get an exception at runtime.

Imagine you’ve defined a Person class like below.

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

Now you could also call the Add method with Person objects. This code compiles fine, but at runtime you’ll have an issue.

Person result = Additions.Add(new Person(),new Person());

As the Person class does not support the + operator, you’ll get an exception at runtime like you see it in the screenshot below. As you can see, the exception says that the + operator cannot be applied to operands of type Person and Person.

So, this means that we have now a generic method, but we cannot limit it to numbers only. We even had to use the dynamic keyword to compile it with the + operator. Now let’s take a look at generic math in C# 11.0.

Generic Math in C# 11.0

In C# 11.0, you can use the INumber<T> interface as a generic type constraint as you see it in the code snippet below. Now this generic Add method compiles as it is, and there’s no need to use the dynamic keyword or any other hack.

public class Additions
{
    public static T Add<T>(T x, T y) where T : INumber<T>
    {
        return x + y;
    }
}

The number types in C# like int and double implement the generic INumber<T> interface. This means that you can use this generic Add method now like below with these types.

int intResult = Additions.Add(3, 7);
double doubleResult = Additions.Add(1.2, 2.4);

If you use the generic Add method with another type that does not implement INumber<T>, like for example with the Person class that I mentioned already before, then you will get a compile error like you see it in the code snippet below.

Now you saw how generic math works in C# 11.0. You just have to use the interface INumber<T> as a type constraint in your generic method, and then everything works. Now let’s dive a bit more into the details to understand what is going on behind the scenes when you use the INumber<T> interface, and to understand how all of this actually works. Let’s start with operators in C#.

Understand Operators in C#

Operators in C# like the + operator are actually mapped to methods behind the scenes. Let’s ensure that you understand how this concept works.

As an example, let’s use the simple Person class below that has just an Age property of type int.

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

Now let’s assume you want to add two Person objects with the + operator like you see it in the code snippet below (Side note: It has been a while since I was 26! ;-)). You could say that you would expect a Person as a result of this operation that has the total age in it’s Age property.

var thomas = new Person { Age = 26 };
var julia = new Person { Age = 24 };

Person personResult = thomas + julia;

Now, of course, the code above does not compile. As you can see in the screenshot below, it gives you the error that the operator + cannot be applied to operands of type Person and Person.

But did you know that you can make this code actually work? You can do this by defining the + operator in the Person class. You see this in the code snippet below. The Person class has now a new static method with the name +. This method has two Person parameters and returns a Person object with the total age. Note also that the method signature uses between the return type and the + the operator keyword. That keyword is necessary to define an operator in C#.

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

    public static Person operator +(Person left, Person right)
    {
        return new Person { Age = left.Age + right.Age };
    }
}

Now, with this + operator defined in the Person class, you can actually use a + to add two Person objects. The code snippet below shows this. It compiles now and the personResult variable contains a Person object that has now the value 50 in its Age property, as this is the sum of 26 and 24.

var thomas = new Person { Age = 26 };
var julia = new Person { Age = 24 };

Person personResult = thomas + julia;

var age = personResult.Age; // age is 50;

Operators are actually a feature that is available since C# 1.0.

Now let’s take a look at how C# 11.0’s static abstract and virtual interface members allow you to define operators in interfaces.

Define Static Abstract and Virtual Interface Members

C# 11.0 introduced the support to define static abstract and static virtual interface members. Those are actually the foundation needed for generic math, as they make it possible to define an operator in an interface.

The code snippet below shows a generic IAddition<T> interface. As you can see, the interface defines a static abstract method to define a + operator. This means that every class that implements this interface needs to provide a + operator method.

public interface IAddition<T> where T : IAddition<T>
{
    static abstract T operator +(T left, T right);
}

Now let’s go back to our Person class on which we implemented already in the previous section a + operator. You can actually just add the IAddition<Person> interface like you see it in the code snippet below, and the body of the class can stay as it is, as the required + operator method is already there.

public class Person : IAddition<Person>
{
    public int Age { get; set; }

    public static Person operator +(Person left, Person right)
    {
        return new Person { Age = left.Age + right.Age };
    }
}

Now, with our new IAddition<T> interface, you can implement a generic Add method like you see it in the code snippet below. Note the generic type constraint that uses the new IAddition<T> interface.

public class Additions
{
    public static T Add<T>(T x, T y) where T : IAddition<T>
    {
        return x + y;
    }
}

This means now that you can use that generic Add method with Person objects. You can see this in the code snippet below.

var thomas = new Person { Age = 26 };
var julia = new Person { Age = 24 };

Person personResult = Additions.Add<Person>(thomas, julia);

var age = personResult.Age; // age is 50;

Now the generic Add method works with all types that implement the custom IAddition<T> interface.

But wouldn’t it be great if the Person class would implement .NET’s interface for the + operator, instead of the custom IAddition<T> interface? This would allow you to implement a generic Add method that works with .NET’s number types, but also with our Person class. Let’s dive into this in the next section.

The .NET Numeric Interfaces

You saw the INumber<T> interface at the beginning of this blog post. We used it to make the Add method generic, respectively to use generic math and the + operator in the Add method. The INumber<T> interface is implemented by .NET’s number types, like int, double, and float. INumber<T> is defined in the namespace System.Numerics. It is a big interface and it extends the INumberBase<T> interface that you see (without its members) in the code snippet below.

public interface INumberBase<TSelf>
        : IAdditionOperators<TSelf, TSelf, TSelf>,
          IAdditiveIdentity<TSelf, TSelf>,
          IDecrementOperators<TSelf>,
          IDivisionOperators<TSelf, TSelf, TSelf>,
          IEquatable<TSelf>,
          IEqualityOperators<TSelf, TSelf, bool>,
          IIncrementOperators<TSelf>,
          IMultiplicativeIdentity<TSelf, TSelf>,
          IMultiplyOperators<TSelf, TSelf, TSelf>,
          ISpanFormattable,
          ISpanParsable<TSelf>,
          ISubtractionOperators<TSelf, TSelf, TSelf>,
          IUnaryPlusOperators<TSelf, TSelf>,
          IUnaryNegationOperators<TSelf, TSelf>
        where TSelf : INumberBase<TSelf>? { ... }

As you can see in the code snippet above, INumberBase<T> extends the IAdditionOperators<T,T,T> interface. The name of this interface sounds pretty much like an addition operator, respectively a + operator.

Indeed, when you look at the IAdditionOperators<T,T,T> interface (see code snippet below), you can see the + operator there. The interface contains a static virtual checked operator. Checked means that this operator will detect overflows, which can happen if you use it with too large numbers. But as it is declared as virtual, there is already an implementation in the interface. This means that a class that implements the IAdditionOperators<T,T,T> interface does not have to provide anything for that virtual member. But the abstract member needs to be implemented when you implement this interface on a class.

public interface IAdditionOperators<TSelf, TOther, TResult>
    where TSelf : IAdditionOperators<TSelf, TOther, TResult>?
{
    static abstract TResult operator +(TSelf left, TOther right);

    static virtual TResult operator checked +(TSelf left, TOther right) => left + right;
}

Now it seems we’re lucky, as the abstract member of the IAdditionOperators<T,T,T> interface looks exactly the same as the operator that we defined before in our own, custom IAddition<T> interface. This means, instead of the IAddition<T> interface you can use .NET’s IAdditionOperators<T,T,T> interface on the Person class like you see it in the code snippet below. Nothing was changed in the body of the class, as the required + operator was already defined in the Person class. Only the new interface was added.

public class Person : IAdditionOperators<Person, Person, Person>
{
    public int Age { get; set; }

    public static Person operator +(Person left, Person right)
    {
        return new Person { Age = left.Age + right.Age };
    }
}

This means now that the Person class implements the same interface for the + operator as the .NET number types like int and double. As our generic Add method does not need any other operators than +, you can use the IAdditionOperators<T,T,T> interface instead of the INumber<T> interface as a generic type constraint, exactly like you see it in the code snippet below.

public class Additions
{
    public static T Add<T>(T x, T y) where T : IAdditionOperators<T, T, T>
    {
        return x + y;
    }
}

This means now that you can use this generic Add method with any number type of .NET, as int, double, or float. This is because they implement INumber<T>, which extends INumberBase<T>, which extends IAdditionOperators<T,T,T>.

But now, you can also use the generic Add method with Person objects, as the Person class also implements the IAdditionOperators<T,T,T> interface.

The code snippet below is a complete top-level C# program. You can create a C# console application with .NET 7 (or later) and paste this code into the Program.cs file to see it in action. As you can see in the code snippet below, the generic Add method is used with int, double, and Person objects. If you use it with a type that does not implement the IAdditionOperators<T,T,T> interface, you will get a compile error. All of this is possible thanks to generic math in C# 11.0.

using System.Numerics;

int intResult = Additions.Add<int>(3, 7);
Console.WriteLine($"Int result: {intResult}");

double doubleResult = Additions.Add<double>(1.2, 2.4);
Console.WriteLine($"Double result: {doubleResult}");

var thomas = new Person { Age = 26 };
var julia = new Person { Age = 24 };

Person personResult = Additions.Add<Person>(thomas, julia);

var age = personResult.Age; // age is 50;

Console.WriteLine($"Person total age: {age}");
Console.ReadLine();

public class Person : IAdditionOperators<Person, Person, Person>
{
    public int Age { get; set; }

    public static Person operator +(Person left, Person right)
    {
        return new Person { Age = left.Age + right.Age };
    }
}

public class Additions
{
    public static T Add<T>(T x, T y) where T : IAdditionOperators<T, T, T>
    {
        return x + y;
    }
}

Summary

You learned in this blog post about generic math in C# 11.0. In simple words, you just define a generic type constraint of type INumber<T> for your generic method like you see it below. Then you can use operators like + in the generic method, and it will work with the different .NET number types like int and double.

public class Additions
{
    public static T Add<T>(T x, T y) where T : INumber<T>
    {
        return x + y;
    }
}

I think generic math is a fantastic extension of the C# language and I hope you enjoyed reading this blog post.

In the next blog post, you will learn about another C# 11.0 feature, which is called List Patterns.

Share this post

Comments (2)

  • Bill Woodruff Reply

    Generic Math: we are still left with the old problem of wanting to perform numerics on two different integral Types, and the messiness of having to use the fact any literal numeric Type can be cast to Double, or, use the awkward ConvertTo facilities.

    Use of ‘dynamic is always a code smell.

    I don’t understand why you give so much space/details to examples that fail.

    January 30, 2023 at 10:23 am
    • Thomas Claudius Huber Reply

      Hey Bill, thank you for your comment.

      Of course dynamic is in this case a code smell. I thought that should’ve been clear by showing that it throws for types that don’t support the + operator.

      The problem you’re describing is for example adding an int and a double?

      January 30, 2023 at 10:54 am

Leave a Reply to Bill Woodruff Cancel reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.