Arithmetic Operations on a Generic (Template) class in Unity
Hello !
Welcome to this first blog post that will talk about doing arithmetic operation on a Template value inside a generic class in Unity.
This was a challenge when creating the Concurrent Value asset.
If you are unfamiliar with generics in C#, check out this page.
The Issue
The C# language doesn’t handle arithmetic operation in a template class, for example this code won’t compile:
public class GenericAdd<T>
{
public T Add_Invalid(T a, T b)
{
return a + b; // error CS0019
}
}
This is normal because the C# language is mostly static/strongly-typed, meaning the compiler check for validity of an operation before the compilation.
Being able to add a constraint on arithmetic operation (using the keyword where) would come with some other issues.
For example you would need two generic class for using a float or an int if you were to use the << operator on T (the template value) because it is undefined for the float type, defeating the purpose of a generic class.
Fortunately, they exists multiple ways to do arithmetic operation in a generic class through less direct means.
We will see them and check which is the more performant. All options presented will work on Unity version 2018.3 or newer.
Testing different options
Cast
The first obvious possibility is to test for the Type of both T parameters, cast them to their basic types, perform the addition, and then cast the result back to typeof(T).
public T Add_Cast(T a, T b)
{
if (a is byte byteA && b is byte byteB)
return (T)(object)(byteA + byteB);
if (a is sbyte sbyteA && b is sbyte sbyteB)
return (T)(object)(sbyteA + sbyteB);
if (a is char charA && b is char charB)
return (T)(object)(charA + charB);
if (a is decimal decimalA && b is decimal decimalB)
return (T)(object)(decimalA + decimalB);
if (a is double doubleA && b is double doubleB)
return (T)(object)(doubleA + doubleB);
if (a is float floatA && b is float floatB)
return (T)(object)(floatA + floatB);
if (a is int intA && b is int intB)
return (T)(object)(intA + intB);
if (a is uint uintA && b is uint uintB)
return (T)(object)(uintA + uintB);
if (a is long longA && b is long longB)
return (T)(object)(longA + longB);
if (a is ulong ulongA && b is ulong ulongB)
return (T)(object)(ulongA + ulongB);
if (a is short shortA && b is short shortB)
return (T)(object)(shortA + shortB);
if (a is ushort ushortA && b is ushort ushortB)
return (T)(object)(ushortA + ushortB);
if (a is Vector2 Vector2A && b is Vector2 Vector2B)
return (T)(object)(Vector2A + Vector2B);
if (a is Vector3 Vector3A && b is Vector3 Vector3B)
return (T)(object)(Vector3A + Vector3B);
if (a is Vector4 Vector4A && b is Vector4 Vector4B)
return (T)(object)(Vector4A + Vector4B);
throw new Exception();
}
This solution come with multiples problems:
- The syntax is messy and prone to errors
- This involve boxing and unboxing the value which is bad for performances
- This doesn’t work for any type not included in the list, any struct created later by you or another user cannot be summed by this function without modifying it
Reflection
Let’s try something else, what about using Reflection to fetch the operator+ method?
First we define another method in our generic class:
We then need to initialize the reflectionAddMethodInfo field into the constructor.
using System.Reflection;
MethodInfo reflectionAddMethodInfo;
public T Add_Reflection(T a, T b)
{
return (T)reflectionAddMethodInfo.Invoke(null, new object[] { a, b });
}
We use “op_Addition” as the method name because that is the name that will be generated by the C# language (you can see a list of all operations and their metadata name here).
This solution also come with big issues: it is based on reflection and therefore pretty slow to execute, and it won’t work for any primitive types (int, float, etc).
Dynamic
Surely the C# language must have a way to bypass the type verification and solve this problem easily ? Yes there is one ! The dynamic keyword introduced with C# v4.
public T Add_Dynamic(T a, T b)
{
dynamic dA = a;
dynamic dB = b;
return dA + dB;
}
Note that in Unity you need to enable .Net 4.x in your player settings to use the dynamic keyword (Edit > Project Settings > Player > Other Settings > Api Compatibility Level).
The dynamic keyword cannot be used if you compile your project using IL2CPP.
This works for any type with a clean syntax, so problem solved right ?
Unfortunately no, this is still a bit slow to execute because it require boxing and unboxing.
Expression
What about using LINQ Expression ?
Expression allow us to create new functions at runtime which is perfect to solve our issue. Let’s do that, first we generate a new function in the class constructor:
using System.Linq.Expressions;
Func<T, T, T> expressionAddLambda;
public GenericAdd()
{
ParameterExpression exprParamA = Expression.Parameter(typeof(T));
ParameterExpression exprParamB = Expression.Parameter(typeof(T));
BinaryExpression addOperation = Expression.Add(exprParamA, exprParamB);
Expression<Func<T, T, T>> lambda = Expression.Lambda<Func<T, T, T>>(addOperation, exprParamA, exprParamB);
expressionAddLambda = lambda.Compile();
}
Then we can simply use the function in a method:
public T Add_Expression(T a, T b)
{
return expressionAddLambda(a, b);
}
This works for any Type and is very performant except for generating the function in the constructor.
This is a very good solution that works for any types but if you want the best performances there is still a better possibility.
Calculator
We will define a Calculator<T> generic class that will include an Add method:
public abstract class Calculator<T>
{
public abstract T Add(T a, T b);
}
Then, you or a user creating a new struct will define non generic class inheriting from Calculator<T>:
public class IntCalculator : Calculator<int>
{
public override int Add(int a, int b)
{
return a + b;
}
}
public class Vector2Calculator : Calculator<Vector2>
{
public override Vector2 Add(Vector2 a, Vector2 b)
{
return a + b;
}
}
We can now use a generic Calculator class in our generic class to do arithmetic operations, we will get it as an argument of our constructor:
Calculator<T> calculator;
public T Add_Calculator(T a, T b)
{
return calculator.Add(a, b);
}
public GenericAdd(Calculator<T> calculator) : this()
{
this.calculator = calculator;
}
This solution is the fastest of them all. Like the first solution it requires that you or the user do some scripting for every value type you’d want to use but it does this in a good object oriented manner. With the user not having to edit another object he didn’t create to extend it’s capabilities.
It will work even if your generic class is part of a dll with no source codes attached.
Testing performances
We will now see how performants are the 5 different solutions we discussed.
On my machine, using an int as the template parameter type the results were as follow:
Method | Time (in s) | Ratio (from fastest) | Ratio (from slowest) |
Cast | 36.54 | 273.14 | 1 |
Reflection | / | / | / |
Dynamic | 10.36 | 77.46 | 0.28 |
Expression | 0.14 | 1.08 | 0.0039 |
Calculator | 0.13 | 1 | 0.0036 |
And using a Vector2:
Method | Time (in s) | Ratio (from fastest) | Ratio (from slowest) |
Cast | 57.70 | 55.90 | 0.98 |
Reflection | 58.38 | 56.56 | 1 |
Dynamic | 11.02 | 10.68 | 0.18 |
Expression | 1.29 | 1.24 | 0.022 |
Calculator | 1.03 | 1 | 0.017 |
From those results we can see that the Cast and Reflection methods are really slow and shouldn’t be used.
We are left with 3 good options:
- The Dynamic method for it’s simplicity
- The Expression method for it’s very good performances and it’s ability to adapt itself to any value type without extra work
- And finally the Calculator method with the best performances for the cost of having to create classes specific to each value type you want to do arithmetic on
Conclusion
You now know the best ways possibles to do arithmetic operations in a generic class on Unity !
In my Concurrent Value asset, I’ve decided to get the bests of all options:
A CalculatorAssigner class will automatically provide an appropriate Calculator for my generic Concurrent class, the user don’t have to choose one, and if no specific Calculators exist for a certain value Type it will default to a Dynamic or Expression one.
Additionally the asset let you automatically generate new Calculator class in an editor window as simply as typing a value type name and clicking on the generate button.
Next article will be about that, how to generate new C# scripts in Unity (meta programming) !
Sources
https://gist.github.com/Slyp05/0bb9cad63d0e83381566b1d67b3a312f.js