Related
I want to implement my own CustomNumber class, and have it comparable to all other primitive number datatypes (int, long, double, float, etc.) using the relational operators.
Is there a way to do that for all of them at once, or do I really have to override the ==, !=, >, <, >= and <= operators aswell as the Equals(T other) method for each single integral datatype?
I think I know how operator overloading works in general, but it feels like there must be some kind of shortcut in order to make my CustomNumber comparable to all primitive number datatypes using relational operators, instead of having to overload each of those 6 operators for each single datatype, which might quickly add up to about 100 overload definitions.
There is no shortcut per se, you have to implement all the operators and functionality for all the types you want to support, it can't read your mind.
Check out the decimal implementation.
For your sanity you will notice not everything needs to be overridden, this is done by the implementation of implicit operators:
public static implicit operator Decimal(byte value)
{
return new Decimal(value);
}
[CLSCompliant(false)]
public static implicit operator Decimal(sbyte value)
{
return new Decimal(value);
}
public static implicit operator Decimal(short value)
{
return new Decimal(value);
}
[CLSCompliant(false)]
public static implicit operator Decimal(ushort value)
{
return new Decimal(value);
}
public static implicit operator Decimal(char value)
{
return new Decimal(value);
}
public static implicit operator Decimal(int value)
{
return new Decimal(value);
}
[CLSCompliant(false)]
public static implicit operator Decimal(uint value)
{
return new Decimal(value);
}
public static implicit operator Decimal(long value)
{
return new Decimal(value);
}
[CLSCompliant(false)]
public static implicit operator Decimal(ulong value)
{
return new Decimal(value);
}
public static explicit operator Decimal(float value)
{
return new Decimal(value);
}
public static explicit operator Decimal(double value)
{
return new Decimal(value);
}
public static explicit operator byte(Decimal value)
{
return ToByte(value);
}
[CLSCompliant(false)]
public static explicit operator sbyte(Decimal value)
{
return ToSByte(value);
}
public static explicit operator char(Decimal value)
{
UInt16 temp;
try
{
temp = ToUInt16(value);
}
catch (OverflowException e)
{
throw new OverflowException(Environment.GetResourceString("Overflow_Char"), e);
}
return (char)temp;
}
public static explicit operator short(Decimal value)
{
return ToInt16(value);
}
[CLSCompliant(false)]
public static explicit operator ushort(Decimal value)
{
return ToUInt16(value);
}
public static explicit operator int(Decimal value)
{
return ToInt32(value);
}
[CLSCompliant(false)]
public static explicit operator uint(Decimal value)
{
return ToUInt32(value);
}
public static explicit operator long(Decimal value)
{
return ToInt64(value);
}
[CLSCompliant(false)]
public static explicit operator ulong(Decimal value)
{
return ToUInt64(value);
}
public static explicit operator float(Decimal value)
{
return ToSingle(value);
}
public static explicit operator double(Decimal value)
{
return ToDouble(value);
}
public static Decimal operator +(Decimal d)
{
return d;
}
public static Decimal operator -(Decimal d)
{
return Negate(d);
}
public static Decimal operator ++(Decimal d)
{
return Add(d, One);
}
public static Decimal operator --(Decimal d)
{
return Subtract(d, One);
}
[System.Security.SecuritySafeCritical] // auto-generated
public static Decimal operator +(Decimal d1, Decimal d2)
{
FCallAddSub(ref d1, ref d2, DECIMAL_ADD);
return d1;
}
[System.Security.SecuritySafeCritical] // auto-generated
public static Decimal operator -(Decimal d1, Decimal d2)
{
FCallAddSub(ref d1, ref d2, DECIMAL_NEG);
return d1;
}
[System.Security.SecuritySafeCritical] // auto-generated
public static Decimal operator *(Decimal d1, Decimal d2)
{
FCallMultiply(ref d1, ref d2);
return d1;
}
[System.Security.SecuritySafeCritical] // auto-generated
public static Decimal operator /(Decimal d1, Decimal d2)
{
FCallDivide(ref d1, ref d2);
return d1;
}
public static Decimal operator %(Decimal d1, Decimal d2)
{
return Remainder(d1, d2);
}
[System.Security.SecuritySafeCritical] // auto-generated
public static bool operator ==(Decimal d1, Decimal d2)
{
return FCallCompare(ref d1, ref d2) == 0;
}
[System.Security.SecuritySafeCritical] // auto-generated
public static bool operator !=(Decimal d1, Decimal d2)
{
return FCallCompare(ref d1, ref d2) != 0;
}
[System.Security.SecuritySafeCritical] // auto-generated
public static bool operator <(Decimal d1, Decimal d2)
{
return FCallCompare(ref d1, ref d2) < 0;
}
[System.Security.SecuritySafeCritical] // auto-generated
public static bool operator <=(Decimal d1, Decimal d2)
{
return FCallCompare(ref d1, ref d2) <= 0;
}
[System.Security.SecuritySafeCritical] // auto-generated
public static bool operator >(Decimal d1, Decimal d2)
{
return FCallCompare(ref d1, ref d2) > 0;
}
[System.Security.SecuritySafeCritical] // auto-generated
public static bool operator >=(Decimal d1, Decimal d2)
{
return FCallCompare(ref d1, ref d2) >= 0;
}
As partial shortcut you can limit your overrides to double which cover all regular "numeric" types at cost of compiler inserted cast. Always comparing to double is dangerous for larger integer values (int and smaller types will be precise always).
class MyType
{
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public static bool operator == (MyType x, double c)
{
// write some real code here - this one does not have a value to compare to.
return c > 42;
}
// you need all several overrides for each operator to behave in expected way
// so calling the same one (a == b)
// from a != b, b != a, b == a is a way to keep them consistent
public static bool operator == (double c, MyType x)
{
return (x == c);
}
public static bool operator != (double c, MyType x)
{
return !(c == x);
}
public static bool operator != (MyType x, double c)
{
return !(x == c);
}
}
Notes
C#/ .NET do not have built in concept of "numeric" type - this is something people ask often enough (i.e. Is there a constraint that restricts my generic method to numeric types?, Generics - where T is a number?).
don't forget to implement IEquatable<T>, IComparable, IComparable<T>...
consider if it is actually useful for your "numbers" to be able to freely mix with other types - especially for comparison with imprecise float/double which already painful enough.
Updated: How could I make a method with two paramerters to take any primitive number type, like int, uint, float, double, etc, (except bool)?
I'm currently using an object, but that means the method can accept any type.
public int[] MyNumberMethod(object a, object b)
{
if (a is int || a is uint || a is short || a is ushort || a is long || a is ulong || a is byte || a is sbyte || a is float || a is double || a is decimal)
{
if (b is int || b is uint || b is short || b is ushort || b is long || b is ulong || b is byte || b is sbyte || b is float || b is double || b is decimal)
return new int[] { Convert.ToInt32(b), Convert.ToInt32(a) };
}
return new int[] { 0, 0 };
}
This might not be as good as the other answers, but another option is to create your own structure where you only allow a value of certain data types:
public struct Number
{
#region Static methods and fields
private static readonly Type[] allowedTypes = new Type[] {
typeof(int), typeof(uint), typeof(short), typeof(ushort),
typeof(long), typeof(ulong), typeof(byte), typeof(sbyte),
typeof(float), typeof(double), typeof(decimal)
};
private static void CheckIsNumber(dynamic val) {
if (Array.IndexOf(allowedTypes, val.GetType()) == -1) { throw new InvalidCastException("Input type must be a number."); }
}
#endregion
#region Constructor
public Number(dynamic Value) {
Number.CheckIsNumber(Value);
_value = Value;
}
#endregion
#region Properties
private dynamic _value;
public dynamic Value {
get { return _value; }
set {
Number.CheckIsNumber(value);
_value = value;
}
}
#endregion
#region Overridden methods
public override bool Equals(object obj) { return _value.Equals(obj); }
public override int GetHashCode() { return _value.GetHashCode(); }
public override string ToString() { return _value.ToString(); }
#endregion
#region Conversion operators - Number
public static implicit operator Number(uint val) { return new Number(val); }
public static implicit operator Number(short val) { return new Number(val); }
public static implicit operator Number(ushort val) { return new Number(val); }
public static implicit operator Number(long val) { return new Number(val); }
public static implicit operator Number(ulong val) { return new Number(val); }
public static implicit operator Number(byte val) { return new Number(val); }
public static implicit operator Number(float val) { return new Number(val); }
public static implicit operator Number(double val) { return new Number(val); }
public static implicit operator Number(decimal val) { return new Number(val); }
#endregion
#region Conversion operators - Misc. data types
public static implicit operator int(Number num) { return (int)num.Value; }
public static implicit operator uint(Number num) { return (uint)num.Value; }
public static implicit operator short(Number num) { return (short)num.Value; }
public static implicit operator ushort(Number num) { return (ushort)num.Value; }
public static implicit operator long(Number num) { return (long)num.Value; }
public static implicit operator ulong(Number num) { return (ulong)num.Value; }
public static implicit operator byte(Number num) { return (byte)num.Value; }
public static implicit operator sbyte(Number num) { return (sbyte)num.Value; }
public static implicit operator float(Number num) { return (float)num.Value; }
public static implicit operator double(Number num) { return (double)num.Value; }
public static implicit operator decimal(Number num) { return (decimal)num.Value; }
#endregion
}
Every time you change the value or create a new instance of the structure it will verify if the input value's data type matches any of the items in the allowedTypes array. If not it will throw an InvalidCastException.
I have also added conversion operators which will let you use this as a normal number, thus you can use it pretty much like you would use any other numerical data type:
Number myNum = 3.5;
myNum += 10.4;
double something = myNum - 6.0;
However keep in mind that you must add a decimal point when working with double, float, etc. or else it will assume that the number is an integer:
Number myNum = 3.5;
myNum -= 2;
MessageBox.Show(myNum.ToString()); //Shows "1" as the second line converts 'myNum' into an integer.
All that said, here's how you would use it for your method:
public int[] MyNumberMethod(Number a, Number b)
{
try {
return new int[] { Convert.ToInt32(b), Convert.ToInt32(a) };
}
catch(InvalidCastException) {
return new int[] { 0, 0 };
}
}
And thanks to the conversion operators you won't need to specify a (Number) conversion. For example:
byte myByte = 133;
//Unnecessary.
MyNumberMethod((Number)17.4, (Number)myByte);
//This works just as fine.
MyNumberMethod(17.4, myByte);
Well since you want to use all primitive types except the bool, how about that?
public int MyNumberMethod<T>(T number) where T : struct
{
if (!(number is bool) && number.GetType().IsPrimitive)
return Convert.ToInt32(number);
return 0;
}
e.g.
MyNumberMethod<short>(5);
All numeric types (except double and float) are implicitly convertible to decimal and float is implicitly convertible to double. So if you make overloads like:
Method(double, double)
Method(decimal, double)
Method(decimal,decimal)
Method(double, decimal)
Your method will be callable with any two numbers but only with any two numbers.
First you must call other function like GenericNumberMethod, that will contain the calling to MyNumberMethod. You must have for each data type an implementacion of GenericNumberMethod with the corresponding parameter
public int MyNumberMethod(object number) {
return Convert.ToInt32(number);
}
public int GenericNumberMethod(int number) {
return MyNumberMethod(number);
}
public int GenericNumberMethod(decimal number) {
return MyNumberMethod(number);
}
I need to return a method in an operator function.
public int Add()
{
return 1;
}
public static int operator +()
{
return Add;
}
I will need to do this for a multiply, subtract and divide operator/function too.
Thanks
You can't declare parameterless operators. You can declare an operator to return an appropriate delegate - e.g. Func<int> - but it would be a pretty odd thing to do, IMO.
If you can tell us more about what you're trying to achieve, we can probably help you to work out a cleaner design.
Here's a pretty strange example overloading the unary + operator:
using System;
class Weird
{
private readonly int amount;
public Weird(int amount)
{
this.amount = amount;
}
private int Add(int original)
{
return original + amount;
}
// Very strange. Please don't do this.
public static Func<int, int> operator +(Weird weird)
{
return weird.Add;
}
}
class Test
{
static void Main(string[] args)
{
Weird weird = new Weird(2);
Func<int, int> func = +weird;
Console.WriteLine(func(3));
}
}
EDIT: If you're just trying to implement a Rational type, you're more likely to want:
public struct Rational
{
// Other members
public Rational Add(Rational other)
{
...
}
public static Rational operator +(Rational left, Rational right)
{
return left.Add(right);
}
}
This is what you SEEM to be trying to do, but your example makes it difficult to tell. So, from your comments in other answers it looks like you want to add, subtract, multiply, divide Rational numbers, which means the result should be a Rational as well (not an int).
Thus, you could define each of your methods, then implement operators to call those. The operators are always static, thus you'd need to check for null and handle as appropriate (in this case, I'll just throw ArgumentNullException):
public class Rational
{
public Rational Add(Rational other)
{
if (other == null) throw new ArgumentNullException("other");
return // <-- return actual addition result here
}
public static Rational operator +(Rational left, Rational right)
{
if (left == null) throw new ArgumentNullException("left");
return left.Add(right);
}
public Rational Subtract(Rational other)
{
if (other == null) throw new ArgumentNullException("other");
return // <-- return actual subtraction result here
}
public static Rational operator -(Rational left, Rational right)
{
if (left == null) throw new ArgumentNullException("left");
return left.Subtract(right);
}
public Rational Multiply(Rational other)
{
if (other == null) throw new ArgumentNullException("other");
return // <-- return actual multiplication result here
}
public static Rational operator *(Rational left, Rational right)
{
if (left == null) throw new ArgumentNullException("left");
return left.Multiply(right);
}
public Rational Divide(Rational other)
{
if (other == null) throw new ArgumentNullException("other");
return // <-- return actual division result here
}
public static Rational operator /(Rational left, Rational right)
{
if (left == null) throw new ArgumentNullException("left");
return left.Divide(right);
}
}
Simple. Just call the Add method:
return Add();
C Sharp - Lesson 18: Overloading Operators
I don't think you can overload the + operator for int's! You would have to create your own wrapper class or struct instead:
public struct MyInt
{
private int _value;
public MyInt(int value)
{
_value = value;
}
public int Value
{
get { return _value; }
}
public static MyInt operator +(MyInt a, MyInt b)
{
return new MyInt(a._value + b._value);
}
public static implicit operator MyInt(int intValue)
{
return new MyInt(intValue);
}
public static explicit operator int(MyInt x)
{
return x.Value;
}
}
Then you are free to do with '+' what ever you want to do with it.
The implicit operator automatically converts int's to MyInt. So you could assign like this: MyInt x = 7;
The explicit operator converts MyInt's to int's like: int i = (int)x; where x is a MyInt.
I am making a wrapper for a "word" in an emulator project. Its meant to put all my cast conversions all in one spot. I was just about to start implement all the overrides for math functions (+,-,/,8, shift, etc.) When it occured to me that shouldn't all the implicit's take care of that? Do I need to over ride >= and <= when I got > < and ==?
I thought I would ask this as while there are plenty of questions relating to how to create them, there aren't many on how much is enough. Here is the code below:
public struct word_t
{
ulong val;
word_t(ulong val) { this.val = val; }
public static implicit operator word_t(int a) { return new word_t((ulong)a); }
public static implicit operator word_t(long a) { return new word_t((ulong)a); }
public static implicit operator word_t(uint a) { return new word_t((ulong)a); }
public static implicit operator word_t(ulong a) { return new word_t((ulong)a); }
public static implicit operator int(word_t a) { return (int)a.val; }
public static implicit operator long(word_t a) { return (long)a.val; }
public static implicit operator uint(word_t a) { return (uint)a.val; }
public static implicit operator ulong(word_t a) { return (ulong)a.val; }
public static bool operator ==(word_t a, word_t b) { return a.val == b.val; }
public static bool operator !=(word_t a, word_t b) { return a.val != b.val; }
public static bool operator >(word_t a, word_t b) { return a.val > b.val; }
public static bool operator <(word_t a, word_t b) { return a.val < b.val; }
public override bool Equals(object obj) {
return obj.Equals(val);
}
public override int GetHashCode() {
return val.GetHashCode();
}
public override string toString() {
return val.ToString();
}
}
My gut tells me to "Trust the compiler" but my head always worries on how efficient it is.
PS I just realized I should override shifts because of the bit shifting of negative number problems, but for right now just imagine shifts just magically work like adds between uint and int.
I recommend this MSDN article: http://msdn.microsoft.com/en-us/library/8edha89s(v=VS.100).aspx
It shows the operators you can overload and any catches. You can overload <= and >= but they must be overloaded in pairs, as is true with == and != as well.
The complex match operators +=, etc are available if +, etc. is overloaded, etc.
>= and <=, however, are separate. That is, overloading > and == does not give you a >= operator implicitly.
I need to define an Interface which has to enforce certain operator overloading to the types which implements it. There doesn't seem an obvious way to do it since operator overloading has to be done using static methods in class. Is there any way to achieve the same effect (using abstract classes or anything else)?
Bit of a hack, but...
You could provide operator overloads in your base class that then call some published abstract methods in one of the classes to do the job there.
public abstract class MyClass
{
public static MyClass operator +(MyClass c1, MyClass c2)
{
return c1.__DoAddition(c2);
}
protected abstract MyClass __DoAddition(MyClass c2);
}
No. The only sensible way to do this would be to have a unit test check use reflection to find all concrete implementations, and then verify this condition. You could also perhaps do something at runtime re the same via a static constructor, but then the question is which static constructor?
Another approach is to drop the operators and use an interface-based approach; for example , if you need T to have +(T,T) then instead of operators have an interface with an Add(T) method. Another advantage here is that interfaces are usable from generics (typically via constraints), where-as using operators from generic code takes some effort.
You could implement the overloading in an abstract base class, but delegate the actually operation specifics to an abstract method. Then this will have to be implemented and the overloading will be don with their implementation.
public abstract class OverLoadingBase
{
public abstract OverLoadingBase DoAdd(OverLoadingBase y);
public static OverLoadingBase operator +(OverLoadingBase x, OverLoadingBase y)
{
return x.DoAdd(y);
}
}
Though I'm not sure if this is complete.
I have done this in the past...
public abstract class BaseClass<TClass> where TClass : BaseClass
{
public static TClass operator +(TClass c1, TClass c2)
{
return c1.DoAddition(c2);
}
protected abstract TClass DoAddition(TClass c2);
}
And then implemented as follows:
public class ConcreteClass : BaseClass<ConcreteClass>
{
protected ConcreteClass DoAddition(ConcreteClass c2)
{
...
}
}
Is there any way in C# to enforce operator overloading in derived classes?
Strictly speaking, types don't "derive" from an interface, they only implement it - but if you are referring to requiring operator overloads in a derived class from a parent class then that can be done by making the parent class generic to allow for variant operator parameter and return types, but this effectively means that the subclass cannot then be subclassed again (without making the subclass generic).
Is there any way to achieve the same effect (using abstract classes or anything else)?
Now, assuming that you are referring to only interfaces, then yes, it's possible! The trick is to not have the operators defined on the interface nor it classes, but in a wrapper-struct - which is used through the magic of operator implicit...
There is a slight catch though, but which is entirely solvable provided you're using C# 6.0 or later... read on!
What you're describing is (another) good use-case for wrapper-structs over open generic interfaces.
This is basically the same thing as Mads Torgersen's suggested approach for implementing "extension-everything": where any object (class) or value (struct), including any interface, is wrapped in a struct invisibly to your program, and that wrapper-struct then adds the required functionality you're after.
...in this case, you want to add "extension operators" to types that will implement an existing interface that defines the underling operations for those operators.
Remember that value-types (struct etc) are "free" in .NET because they don't incur a GC heap allocation.
To make this work, first define the interface with the operations you want to support.
This interface has generic type parameters to allow for variant return types, and for accessing the "raw value":
public interface IOperable<TImpl,TValue> : IEquatable<TImpl>, IComparable<TImpl>
where TImpl : IOperable<TImpl,TValue>
where TValue : notnull
{
Operable<TImpl,TValue> Op { get; }
TImpl CreateFor( TValue other );
TImpl Self { get; }
TValue Value { get; }
TImpl Add( TValue other );
TImpl Subtract( TValue other );
TImpl Multiply( TValue other );
TImpl Divide( TValue other );
TImpl Remainder( TValue other );
TImpl Inverse();
}
Then define the wrapper-struct (struct Operable<TImpl,TValue>).
The wrapper-struct has the static operator methods which all accept and return the the same Operable<TImpl,TValue>.
But most importantly: struct Operable also has implicit operator defined for implicit conversion to-and-from TImpl and to TValue, which is what helps make this approach usable.
Implicit conversion from TValue isn't possible because there's no way of knowing what TImpl would be, but you can define operators on struct Operable<> that allow raw TValue for either of the operands, that way it can infer TImpl from the other operand.
// Note that `struct Operable<T>` does not implement IOperable<T>.
public struct Operable<TImpl,TValue>
where TImpl : IOperable<TImpl,TValue>
where TValue : notnull
{
#region Operators (Self-to-Self)
public static Operable<TImpl,TValue> operator +(Operable<TImpl,TValue> lhs, Operable<TImpl,TValue> rhs)
{
TImpl result = lhs.Self.Add( rhs.Value );
return result;
}
public static Operable<TImpl,TValue> operator -(Operable<TImpl,TValue> lhs, Operable<TImpl,TValue> rhs)
{
return lhs.Self.Subtract( rhs.Value );
}
public static Operable<TImpl,TValue> operator -(Operable<TImpl,TValue> self)
{
return self.Self.Inverse();
}
public static Operable<TImpl,TValue> operator *(Operable<TImpl,TValue> lhs, Operable<TImpl,TValue> rhs)
{
return lhs.Self.Multiply( rhs.Value );
}
public static Operable<TImpl,TValue> operator /(Operable<TImpl,TValue> lhs, Operable<TImpl,TValue> rhs)
{
return lhs.Self.Divide( rhs.Value );
}
public static Operable<TImpl,TValue> operator %(Operable<TImpl,TValue> lhs, Operable<TImpl,TValue> rhs)
{
return lhs.Self.Remainder( rhs.Value );
}
#endregion
#region Operators (Self + TValue)
public static Operable<TImpl,TValue> operator +(Operable<TImpl,TValue> lhs, TValue rhs)
{
TImpl result = lhs.Self.Add( rhs );
return result;
}
public static Operable<TImpl,TValue> operator -(Operable<TImpl,TValue> lhs, TValue rhs)
{
return lhs.Self.Subtract( rhs );
}
public static Operable<TImpl,TValue> operator *(Operable<TImpl,TValue> lhs, TValue rhs)
{
return lhs.Self.Multiply( rhs );
}
public static Operable<TImpl,TValue> operator /(Operable<TImpl,TValue> lhs, TValue rhs)
{
return lhs.Self.Divide( rhs );
}
public static Operable<TImpl,TValue> operator %(Operable<TImpl,TValue> lhs, TValue rhs)
{
return lhs.Self.Remainder( rhs );
}
#endregion
#region Operators (TValue + Self)
public static Operable<TImpl,TValue> operator +(TValue lhs, Operable<TImpl,TValue> rhs)
{
TImpl lhs2 = rhs.Self.CreateFor( lhs );
TImpl result = lhs2.Self.Add( rhs.Value );
return result;
}
public static Operable<TImpl,TValue> operator -(TValue lhs, Operable<TImpl,TValue> rhs)
{
TImpl lhs2 = rhs.Self.CreateFor( lhs );
TImpl result = lhs2.Self.Subtract( rhs.Value );
return result;
}
public static Operable<TImpl,TValue> operator *(TValue lhs, Operable<TImpl,TValue> rhs)
{
TImpl lhs2 = rhs.Self.CreateFor( lhs );
TImpl result = lhs2.Self.Multiply( rhs.Value );
return result;
}
public static Operable<TImpl,TValue> operator /(TValue lhs, Operable<TImpl,TValue> rhs)
{
TImpl lhs2 = rhs.Self.CreateFor( lhs );
TImpl result = lhs2.Self.Divide( rhs.Value );
return result;
}
public static Operable<TImpl,TValue> operator %(TValue lhs, Operable<TImpl,TValue> rhs)
{
TImpl lhs2 = rhs.Self.CreateFor( lhs );
TImpl result = lhs2.Self.Remainder( rhs.Value );
return result;
}
#endregion
public static implicit operator Operable<TImpl,TValue>( TImpl impl )
{
return new Operable<TImpl,TValue>( impl );
}
// public static implicit operator TValue( Operable<TImpl,TValue> self )
// {
// return self.Value;
// }
public static implicit operator TImpl( Operable<TImpl,TValue> self )
{
return self.Self;
}
public Operable( TImpl impl )
{
this.Self = impl;
this.Value = impl.Value;
}
public TImpl Self { get; }
public TValue Value { get; }
}
So for example, supposing we have a custom number type that we want to enforce at compile-time the operators described above...
public struct ComplexNumber
{
public Double Real;
public Double Complex;
}
Simply make it implement IOperable, so this implementation defines most arithmetic operations on complex numbers:
public struct ComplexNumber : IOperable<ComplexNumber,ComplexNumber>
{
public static implicit operator ComplexNumber( ( Double r, Double i ) valueTuple )
{
return new ComplexNumber( valueTuple.r, valueTuple.i );
}
public Double Real;
public Double Imaginary;
public ComplexNumber( Double real, Double imaginary )
{
this.Real = real;
this.Imaginary = imaginary;
}
public Double Magnitude => Math.Sqrt( ( this.Real * this.Real ) + ( this.Imaginary * this.Imaginary ) );
public ComplexNumber Conjugate => new ComplexNumber( real: this.Real, imaginary: -this.Imaginary );
public ComplexNumber Self => this;
public ComplexNumber Value => this;
public Operable<ComplexNumber,ComplexNumber> Op => new Operable<ComplexNumber,ComplexNumber>( this.Value );
public ComplexNumber Add(ComplexNumber other)
{
Double r = this.Real + other.Real;
Double i = this.Imaginary + other.Imaginary;
return new ComplexNumber( r, i );
}
public ComplexNumber Subtract(ComplexNumber other)
{
Double r = this.Real - other.Real;
Double i = this.Imaginary - other.Imaginary;
return new ComplexNumber( r, i );
}
public ComplexNumber Multiply(ComplexNumber other)
{
// (a+bi) * (c+di) == a(c + di) + bi(c + di)
// == (ac - bd) + (ad + bc)i
Double a = this.Real;
Double b = this.Imaginary;
Double c = other.Real;
Double d = other.Imaginary;
//
Double r = ( a * c ) - ( b * d );
Double i = ( a * d ) + ( b * c );
return new ComplexNumber( r, i );
}
public ComplexNumber Divide(ComplexNumber other)
{
// Division is the same as multiplying by the conjugate.
ComplexNumber conjugate = other.Conjugate;
ComplexNumber numerator = this.Value.Multiply( conjugate );
ComplexNumber denominator = other.Multiply( conjugate );
if( denominator.Imaginary == 0 )
{
Double d = denominator.Real;
Double newReal = numerator.Real / d;
Double newImag = numerator.Imaginary / d;
return new ComplexNumber( newReal, newImag );
}
else
{
throw new NotSupportedException( "Non-trivial complex division is not implemented." );
}
}
public ComplexNumber Remainder(ComplexNumber other)
{
// Remainder isn't the same as Modulo (fun-fact: in C89 the `%` operator is for remainder, not modulo!)
// Anyway, implementing Remainder for complex numbers is non-trivial.
// As is Modulo: https://math.stackexchange.com/questions/274694/modulo-complex-number
// So just throw:
throw new NotSupportedException( "The remainder operation for complex-numbers is not implemented." );
}
public ComplexNumber Inverse()
{
return new ComplexNumber( real: -this.Real, imaginary: -this.Imaginary );
}
#region IEquatable + IComparable
public ComplexNumber CreateFor(ComplexNumber other)
{
return other;
}
public Int32 CompareTo( ComplexNumber other )
{
return this.Magnitude.CompareTo( other.Magnitude );
}
public override Boolean Equals( Object? obj )
{
return obj is ComplexNumber other && this.Equals( other: other );
}
public override Int32 GetHashCode()
{
return base.GetHashCode();
}
public Boolean Equals( ComplexNumber other )
{
return this.Real == other.Real && this.Imaginary == other.Imaginary;
}
public override String ToString()
{
if( this.Imaginary < 0 )
{
return String.Format( CultureInfo.InvariantCulture, "({0}{1}i)", this.Real, this.Imaginary );
}
else
{
return String.Format( CultureInfo.InvariantCulture, "({0}+{1}i)", this.Real, this.Imaginary );
}
}
#endregion
}
So this should be able to be used like so:
public static void Main()
{
ComplexNumber a = ( r: 6, i: 4 );
ComplexNumber b = ( r: 8, i: -2 );
ComplexNumber c = a + b;
Console.WriteLine( "{0} + {1} = {2}", a, b, c );
}
...but it doesn't!
The problem is that we need either a or b to be implicitly promoted to Operable<ComplexNumber,ComplexNumber> so that the overloaded + operator will be invoked.
The quick-and-dirty workaround is to use that Op property on the innermost operand (according to operator precedence rules) to trigger the implicit conversion to Operable<> and the compiler takes care of the rest, including implicit conversion back to ComplexNumber:
So this:
public static void Main()
{
ComplexNumber a = ( r: 6, i: 4 );
ComplexNumber b = ( r: 8, i: -2 );
ComplexNumber c = a.Op + b;
Console.WriteLine( "{0} + {1} = {2}", a, b, c );
}
...gives me the expected output of (6+4i) + (8--2i) = (14+2i).
...which then works with expressions of any length and complexity, just remember to use .Op on the first operation, not the left-most (in this case, both b.Op and d.Op due to them being independent operations:
public static void Main()
{
ComplexNumber a = ( r: 6, i: 4 );
ComplexNumber b = ( r: 8, i: -2 );
ComplexNumber c = ( r: 1, i: 9 );
ComplexNumber d = ( r: 9, i: 5 );
ComplexNumber e = ( r: -2, i: -1 );
ComplexNumber f = a + b.Op * c - ( d.Op / e );
Console.WriteLine( "{0} + {1} * {2} - ( {3} / {4} ) = {5}", a, b, c, d, e, f );
}
Of course, the .Op part is still an ugly wart, but what can be done about that?
Well, the answer lies in two parts:
A Roslyn code analysis type that verifies that any type that implements IOperable also has the operators overloaded.
This is more of a solution to your original question: a way to "enforce" that a type that implements an interface also overloads the operators.
This isn't perfect, though: as a type in an external assembly could still be legally compiled without the operators overloaded. Though at least with struct Operand you can still take advantage of overloaded operators (albeit with the .Op wart)
Use Roslyn code-generation to auto-generate the necessary operators in a partial type provided elsewhere, including auto-generating operator implicit to accommodate any external types that haven't overloaded the operators.
Part 1 is straightforward, here's a simple Roslyn analyzer that will raise a warning (or error, at your discretion):
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace You
{
[DiagnosticAnalyzer( LanguageNames.CSharp )]
public class OperatorOverloadingAnalyzer : DiagnosticAnalyzer
{
public static ImmutableArray<String> FixableDiagnosticIds { get; } = ImmutableArray.Create( "0xDEADBEEF0001" );
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => OperatorOverloadingAnalyzerInfo.AsArray;
public override void Initialize( AnalysisContext context )
{
context.ConfigureGeneratedCodeAnalysis( GeneratedCodeAnalysisFlags.None );
context.EnableConcurrentExecution();
context.RegisterSymbolAction( AnalyzeSymbol, SymbolKind.NamedType );
}
private static void AnalyzeSymbol( SymbolAnalysisContext context )
{
if( IsClassOrStructThatImplementsIOperable( context.Symbol, out INamedTypeSymbol? namedTypeSymbol ) )
{
if( !HasOperators( namedTypeSymbol ) )
{
Diagnostic diagnostic = Diagnostic.Create( OperatorOverloadingAnalyzerInfo.Descriptor, location: namedTypeSymbol.Locations[0], namedTypeSymbol.Name );
context.ReportDiagnostic( diagnostic );
}
}
}
private static Boolean IsClassOrStructThatImplementsIOperable( ISymbol symbol, [NotNullWhen(true)] out INamedTypeSymbol? namedTypeSymbol )
{
if( symbol is INamedTypeSymbol ins )
{
namedTypeSymbol = ins;
if( namedTypeSymbol.TypeKind == TypeKind.Class || namedTypeSymbol.TypeKind == TypeKind.Struct )
{
if( namedTypeSymbol.AllInterfaces.Any( iface => iface.Name == "IOperable" ) )
{
return true;
}
}
return false;
}
else
{
namedTypeSymbol = null;
return false;
}
}
private static readonly HashSet<String> _opMemberNames = new HashSet<String>( StringComparer.Ordinal )
{
"op_Addition",
"op_Division",
"op_Multiply",
"op_Subtraction"
};
private static Boolean HasOperators( INamedTypeSymbol type )
{
Int32 matchCount = 0;
foreach( String memberName in type.MemberNames )
{
if( _opMemberNames.Contains( memberName ) )
{
matchCount++;
}
}
return matchCount == 4;
}
}
}
Just copy and paste the above into the stock Roslyn analyzer project in VS's project templates and off y'go.
Part 2... is too much effort for me now, so consider that an exercise for the reader.
Since its operator can only be overloaded and not overridden its quite difficult. The best solution I can think of is use an abstract class and overloading like this.
public abstract class MyBase
{
public abstract MyBase Add(MyBase right);
public abstract MyBase Subtract(MyBase right);
public static MyBase operator +(MyBase x, MyBase y)
{
//validation here
return x.Add(y);
}
public static MyBase operator -(MyBase x, MyBase y)
{
//validation here
return x.Subtract(y);
}
}
I'd do something like:
public abstract class Scalar<This> where This : Scalar<This>
{
public static This operator +(Scalar<This> a, This b) => a.Add(b);
public abstract This Add(This another);
...
}
Then you can inherit Scalar as:
public sealed class Rational : Scalar<Rational>
{
public override Rational Add(Rational another)
{
...
}
...
}
And that's it:
Rational a = ...;
Rational b = ...;
Rational sum = a + b;