Namespace: MiscUtil
Types involved: Operator, Operator<T>
Note: these classes were written by Marc Gravell. I claim no credit for their brilliance! Jon.
The Operator
class provides high-performance support for the
common operators (+, -, *, etc), but using generics - meaning it can for for any
type with suitable operators defined. For a full discussion of why this isn't
provided by default, see the generic operators discussion page.
With regular C# you might use the operators, for instance:
There is no native equivalent for generics; however, the Operator
class provides an alternative:
The Add
method is actually a generic method:
The compiler is able to use type inference to work out the type of T
automatically.
The system uses the Expression
class from .NET 3.5, which uses standard operators
declared on types, as well as IL-level operators (such as Int32
or Single
)
and Nullable<T>
- making it very versatile.
A brief aside for Nullable<T>
; recall that the rules for lifted operators
with Nullable<T>
mean that the result of a lifted operator with a null
operand is generally null. You should give consideration to how you want to treat nulls,
and code accordingly. Fortunately, this is simple, and the JIT-compiler does a good job of
removing unnecessary defensive code.
Also; commonly code needs to consider zero - particularly as the seed for sum operations;
however, default(T)
is not zero if T
is nullable - it is
null. To help with this, an Operator<T>.Zero
static property is provided,
which will be the true zero for T
.
The two following examples illustrate most of the key points for working with the Operator
class; they are heavily simplified (but generic) implementations of the Sum()
and
Average()
methods from LINQ.
First we'll consider Add()
; this discards any null values found and returns the
sum of the non-null values; if no (non-null) values are found, zero is returned in all cases.
This can be implemented very simply:
We start at zero, and simply accumulate the values. If called with a non-nullable
struct type (Int32
for example), then the "if" test will be removed by the JIT,
simply calling the "true" portion (since such a value can never be null).
As a slightly more involved example, we'll consider Average()
; like Sum()
this
also discards any null values, and returns the mean average (sum divided by the number of
non-null values). Since an integer division (T
divided by Int32
) is a
very common requirement, an additional Operator
method is provided for convenience.
Also, note that the behaviour for the empty-case (where no non-null values are found)
varies: if T
is nullable, it returns null; otherwise it throws an exception.
As per the expected behaviour, this starts similar to Sum()
, but also tracking the
count of the values. If we have a non-zero count, we use DivideInt32()
to obtain the
average. Otherwise we use default(T)
; if this is null, then T
is nullable
and we can return this value (null); if not, throw an exception.
While these examples illustrate the ease of use, you are not limited to these operations.
All of the most common operators are available:
public static T Add<T>(T value1, T value2)
public static T Subtract<T>(T value1, T value2)
public static T Multiply<T>(T value1, T value2)
public static T Divide<T>(T value1, T value2)
public static T DivideInt32<T>(T value, int divisor)
public static T Negate<T>(T value)
public static T Zero { get; }
(Member of Operator<T>
) Comparer<T>
and EqualityComparer<T>
):public static bool Equal<T>(T value1, T value2)
public static bool NotEqual<T>(T value1, T value2)
public static bool GreaterThan<T>(T value1, T value2)
public static bool LessThan<T>(T value1, T value2)
public static bool GreaterThanOrEqual<T>(T value1, T value2)
public static bool LessThanOrEqual<T>(T value1, T value2)
public static T Not<T>(T value)
public static T Or<T>(T value1, T value2)
public static T And<T>(T value1, T value2)
public static T Xor<T>(T value1, T value2)
public static TTo Convert<TFrom, TTo>(TFrom value)
If a critical operator has been missed (and can be sensibly defined in generic terms) then please let me know.
For advanced scenarios, there is also limited support for operators with very different operand types
(think DateTime + Timespan
=> DateTime
).
Sometimes you can design around the need to use them, but a limited subset are provided for convenience:
public static TArg1 AddAlternative(TArg1 value1, TArg2 value2)
public static TArg1 SubtractAlternative(TArg1 value1, TArg2 value2)
public static TArg1 MultiplyAlternative(TArg1 value1, TArg2 value2)
public static TArg1 DivideAlternative(TArg1 value1, TArg2 value2)
The main slight weakness here is that is cannot provide compile-time safety; it won't
attempt to resolve the operators until runtime. In reality this is rarely an issue, and
is comparable to saying that Comparer<T>.Default
is unreliable
because T
might not implement IComparable<T>
(etc).
It is true, but then: you aren't likely to try and call Sum()
(etc) on such a type.
It doesn't support scenarios where the return is unrelated to either operand - i.e.
TReturn Operator(TArg1, TArg3) {...}
. Again, this is unlikely to be an
issue in reality; if somebody has a genuine use-case for this, please let me know!
Back to the main MiscUtil page.