Generic Math Support in C# 11
What is Generic Math support in C# 11, and how do we take advantage of it? Let's dig in and find out! (part 3 of 3)
- What is a static abstract interface method in C#?
- Overloading arithmetic, equality, and comparison operators in C#
- Generic Math Support in C# 11
This is post 3 in a 3-part series building up to a new C# 11 feature called Generic Math. First though, it might be helpful to read two other posts, to get familiar with static abstract members (also new to C# 11) and overloading operators (not new, but useful).
Generics
Since generics were introduced in version 2.0 back in 2005, it's been enhanced a few times over the years, most recently in C# 11. Generics let us write code in a way that the exact type of the data doesn't have to be known right away.
You can, for example, write a base class with generics that allows any class inheriting from it to define an "identifier" that's a different type, like this code does. Notice how Box uses an integer for its identifier, Crate uses a string, and Folder uses a Guid. We don't need to define the same GetIdentifier()
method in each of the classes either - nice and DRY.
public class Container<T>
{
public T Identifier { get; set; }
public string GetIdentifier() => $"The identifier is: {Identifier}";
}
public class Box : Container<int>
{
public Box(int identifier)
{
Identifier = identifier;
}
}
public class Crate : Container<string>
{
public Crate(string identifier)
{
Identifier = identifier;
}
}
public class Folder : Container<Guid>
{
public Folder(Guid identifier)
{
Identifier = identifier;
}
}
[Test]
public void GetIdentifierWorksForAllTypesOfContainers()
{
var boxId = 4; // chosen by fair dice roll; randomness guaranteed
var crateId = "absolutely_unique_id";
var folderId = Guid.NewGuid();
var box = new Box(boxId);
var crate = new Crate(crateId);
var folder = new Folder(folderId);
Assert.Multiple(() =>
{
Assert.That(box.GetIdentifier(), Is.EqualTo($"The identifier is: {boxId}"));
Assert.That(crate.GetIdentifier(), Is.EqualTo($"The identifier is: {crateId}"));
Assert.That(folder.GetIdentifier(), Is.EqualTo($"The identifier is: {folderId}"));
});
}
You can use generics in interfaces too, like the .NET framework does with IEnumerable<T> (heavily used with LINQ).
Here's a slightly different version of the above code, replacing the generic base class with a generic interface. Anything that accepts an IContainer<T>
can rest assured that the classes implementing the interface will have an identifier (whose type may vary per class, like with the int, string, and Guid above) and a method that prints a description about the identifier.
public interface IContainer<T>
{
T Identifier { get; set; }
string GetDescription();
}
public class Box : IContainer<int>
{
public int Identifier { get; set; }
public string GetDescription() => $"The box id is {Identifier}.";
}
public class Crate : IContainer<Guid>
{
public Guid Identifier { get; set; }
public string GetDescription() => $"The guid is: {Identifier}";
}
[Test]
public void GetDescriptionWorksForAllTypesOfContainers()
{
var boxId = 4; // chosen by fair dice roll; randomness guaranteed
var crateId = Guid.NewGuid();
var box = new Box { Identifier = boxId };
var crate = new Crate { Identifier = crateId };
Assert.Multiple(() =>
{
Assert.That(box.GetDescription(), Is.EqualTo($"The box id is {boxId}."));
Assert.That(crate.GetDescription(), Is.EqualTo($"The guid is: {crateId}"));
});
}
Generic Math
One thing we haven't really been able to do before, though, is add "static" members to an interface. Actually, since C# 8 we've apparently been able to add static methods to interfaces as long as they declare a default body. I'm sure there's a good reason for it, but I haven't used it yet.
Anyway, since overloading an operator requires defining a static method on a class, and there's never been a way to specify a static abstract member in an interface (aka one without a default body), it hasn't been possible to have an interface require that classes overload certain operators. Until now.
As of C# 11, you can add static abstract members to interfaces, which means you can require classes to have to implement one or more overloaded operators. Although you can certainly test this with your own interfaces (learn more here), you can also make use of the new interfaces that C# 11 has given us. There's IAdditionOperators and IComparisonOperators, as well as quite a few others. Let's take a closer look...
Practical Examples
First, let's look at a "Fraction" class that implements the two interfaces I mentioned above (inspired by the Fraction struct in Microsoft's operator overloading docs).
Implementing the IAdditionOperators
interface forces overloading the +
operator. For this class, I'm performing some simple math on two fractions' numerators and denominators, and not bothering with edge-cases (like a zero denominator).
Implementing the IComparisonOperators
interface forces overloading all the other operators in the following example. To keep things simple, I'm just performing simple division (gotta watch out for the effects of integer division!) and using the decimal value for comparisons.
Here's another class, this time one that represents a Folder that can hold a collection of file paths.
To implement the IAdditionOperators
interface and overload the +
operator, I'm just creating a new Folder and adding all the files to it.
To implement the IComparisonOperators
interface and overload the other operators, I'm just using a count of the files. If one Folder has less files, it's considered less. If two Folders have an equal number of files, even if the paths are totally different, they're still equal. That'd be problematic in reality, but oh well... it'll work for what we need to do. :)
Once we've got a couple classes that implement the new interfaces... what then? Well, we can write utilities against those interfaces, to perform mathematical operations on objects without needing to know what the exact type is ahead of time.
At runtime, when the generic methods below are called, the code will grab the actual implementation of whatever class is being passed to it and figure out what it means to "sum" up fractions, or find the "least" box in a collection of boxes - according to how you defined it.
And that's it! That's the new Generic Math feature in C# 11. As I mentioned earlier, all the examples here are available on GitHub, if you want to mess with it more.
I found a great video on YouTube demonstrating some of these same concepts, so if that's more your style, go watch Jasper Kent's tutorial too. Sometimes, seeing something presented in multiple ways drives it home that much more.. at least it does for me.
One thing I didn't show is that you can reference the INumber<T> interface, which references most (all?) of the other new interfaces. You'll have to implement dozens and dozens of methods, but then your new class can basically be treated like any other number type in C#. You can read more about that, and Generic Math in general, in the Microsoft docs.
If you found this content useful, and want to learn more about a variety of C# features, check out this GitHub repo, where you'll find links to plenty more blog posts and practical examples!
Spread the Word