I have the following setup:
public abstract class HyperComplexNumber {
public HyperComplexNumber(double real, double imaginary) {
Real = real;
Imaginary = imaginary;
}
public double Real;
public double Imaginary;
public static HyperComplexNumber operator (HyperComplexNumber left, HyperComplexNumber right)
{
return new HyperComplexNumber(left.Real right.Real, left.Imaginary right.Imaginary);
}
}
public class ComplexNumber : HyperComplexNumber {
// Some code here
}
public class SplitComplexNumber : HyperComplexNumber {
// Some code here
}
public class DualNumber : HyperComplexNumber {
// Some code here
}
Now the problem is, that inside the operator definition, it's not possible the instantiate HyperComplexNumber
(since it's abstract). I see multiple ways to solve this, but none of them seems good enough.
One way is to implement the operator in each child class separately, but this just leads to redundant code, since all of the child classes would essentially have the same code in the operator definition.
Another way could be to use reflection to instantiate the child class, but I heard that using reflection is usually a code smell. So that's why I'm asking this question: what would be the best way to solve this?
CodePudding user response:
In your case I would remove operator from the base abstract class, because it:
Might be used incorrectly (when operands are of different subtypes, like
ComplexNumber
andDualNumber
).Has fixed return type of base
HyperComplexNumber
, so it's not convenient to use anyway. For example, if I haveComplexNumber a
andComplexNumber b
, I'd expect inc = a b
forc
to be of typeComplexNumber
at compile time, without any explicit casting. Now variablec
will always beHyperComplexNumber
(even though actual type might beComplexNumber
).
So I'd remove this operator from base class and then add it to every subclass. Yes it's a bit of code duplication, but in this case it's justified, because now those operators are actually useful and work as you would expect.
You can reduce code duplication a bit with something like:
public abstract class HyperComplexNumber {
public HyperComplexNumber(double real, double imaginary) {
Real = real;
Imaginary = imaginary;
}
public double Real;
public double Imaginary;
protected static (double real, double im) Add(HyperComplexNumber left, HyperComplexNumber right) {
return (left.Real right.Real, left.Imaginary right.Imaginary);
}
}
public class ComplexNumber : HyperComplexNumber {
public ComplexNumber(double real, double imaginary) : base(real, imaginary) {
}
public static ComplexNumber operator (ComplexNumber left, ComplexNumber right) {
var (real, im) = Add(left, right);
return new ComplexNumber(real, im);
}
}
Though obviously in this case it doesn't reduce much.
CodePudding user response:
For now I see 2 solutions if you do not create overrides for child types:
First — only extension methods
Create constructor without parameters and make properties init-only (if you don't want to change them after object creation):
public HyperComplexNumber()
{
}
public double Real { get; init; }
public double Imaginary { get; init; }
Create type that represents sum of complex numbers of different types (if you want to be able to sum different types):
public class SumOfDifferentNumbers : HyperComplexNumber
{
// Some code here
}
Create 2 extension methods:
public static class HyperComplexNumberExtensions
{
public static TNumber Sum<TNumber>(this TNumber left, TNumber right)
where TNumber : HyperComplexNumber, new()
{
return new TNumber
{
Real = left.Real right.Real,
Imaginary = left.Imaginary right.Imaginary
};
}
// If you want to sum different types
public static TResult Sum<TFirst, TSecond, TResult>(this TFirst left, TSecond right)
where TFirst : HyperComplexNumber
where TSecond : HyperComplexNumber
where TResult : HyperComplexNumber, new()
{
return new TResult
{
Real = left.Real right.Real,
Imaginary = left.Imaginary right.Imaginary
};
}
}
And use them like this:
var left = new ComplexNumber
{
Real = 1,
Imaginary = 2
};
var right = new ComplexNumber
{
Real = 1,
Imaginary = 2
};
var sum1 = left.Sum(right);
var rightOfOtherType = new DualNumber
{
Real = 1,
Imaginary = 2
};
var sum2 = left.Sum<ComplexNumber, DualNumber, SumOfDifferentNumbers>(rightOfOtherType);
Second — with operator
Do all steps from the first solution.
Add the following code to the HyperComplexNumberExtensions
(if you want to be able to sum different types):
public static readonly MethodInfo[] AllSumMethods =
typeof(HyperComplexNumberExtensions)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(m => m.Name == nameof(HyperComplexNumberExtensions.Sum))
.ToArray()
Define sum operator in HyperComplexNumber
like this:
public static HyperComplexNumber operator (HyperComplexNumber left, HyperComplexNumber right)
{
MethodInfo sumMethod;
MethodInfo genericImplementation;
if (left.GetType() == right.GetType())
{
sumMethod = HyperComplexNumberExtensions.AllSumMethods.First(m => m.GetGenericArguments().Length == 1);
genericImplementation = sumMethod.MakeGenericMethod(left.GetType());
}
else
{
sumMethod = HyperComplexNumberExtensions.AllSumMethods.First(m => m.GetGenericArguments().Length == 3);
genericImplementation = sumMethod.MakeGenericMethod(
left.GetType(),
right.GetType(),
typeof(SumOfDifferentNumbers));
}
return (HyperComplexNumber)genericImplementation.Invoke(null, new[] { left, right });
}
And use it like this (it is assumed that before this is the code from the previous example):
var rightOfOtherType = new DualNumber
{
Real = 1,
Imaginary = 2
};
sum1 = (ComplexNumber)(left right);
sum2 = (SumOfDifferentNumbers)(left rightOfOtherType);
Remarks
You can define sum of different types without third generic parameter and return concrete type, for example SumOfDifferentNumber
.
If you want to sum only same types, you can throw exception on non-equality of types in sum operator.
CodePudding user response:
The crux of your problem is instantiation of an abstract class. I agree with @EvK that operator overloading is not the solution you should be looking to implement. However, if you must, for whatever reasons, try this:
public static HyperComplexNumber operator (HyperComplexNumber left, HyperComplexNumber right)
{
var sumValue = (HyperComplexNumber) left.MemberWiseClone();
sumValue.Real = right.Real;
sumValue.Imaginary = right.Imaginary;
return sumValue;
}
But standard caveats apply. These include that you have only these two values, they are primitive etc etc. I hope you get the gist.
CodePudding user response:
Here is how I have done it in the past. I used a generic specification for a future class. Below TComplex
is to specified by the deriving classes as their own, but it can be used in the base class to describe those future classes.
An example implementation of complex and dual numbers is below with algebra, formatting, and some other goodies.
Complex numeric type usage
static class Program
{
static void Main(string[] args)
{
var a = new ComplexNumber(1.0, -5.0);
var b = new ComplexNumber(1.0, 5.0);
Console.WriteLine($"a = {a}, {a.GetType().Name}");
Console.WriteLine($"b = {b}, {b.GetType().Name}");
//a = 1-5i, ComplexNumber
//b = 1 5i, ComplexNumber
Console.WriteLine($"a b = {a b}");
Console.WriteLine($"a - b = {a - b}");
Console.WriteLine($"a * b = {a * b}");
// a b = 2
// a - b = -10i
// a * b = 26
var c = 2.0 * a 3.0 * b;
Console.WriteLine($"c = {c}, {c.GetType().Name}");
// c = 5 5i, ComplexNumber
var p = new DualNumber(1.0, -5.0);
var q = new DualNumber(1.0, 5.0);
Console.WriteLine($"p = {p}, {p.GetType().Name}");
Console.WriteLine($"q = {q}, {q.GetType().Name}");
//p = 1 - 5ε, DualNumber
//q = 1 5ε, DualNumber
Console.WriteLine($"p q = {p q}");
Console.WriteLine($"p - q = {p - q}");
Console.WriteLine($"p * q = {p * q}");
// p q = 2
// p - q = -10ε
// p * q = 1
}
}
Complex numeric type implementation
public abstract class HyperComplexNumber<TComplex>
where TComplex : HyperComplexNumber<TComplex>, new()
{
protected static readonly Random rng = new Random();
protected HyperComplexNumber(double real, double imaginary)
{
Real = real;
Imaginary = imaginary;
}
public double Real { get; set; }
public double Imaginary { get; set; }
public abstract string Symbol { get; }
public TComplex Scale(double factor)
{
var t = new TComplex
{
Real = factor * Real,
Imaginary = factor * Imaginary
};
return t;
}
public TComplex Add(TComplex other)
{
var t = new TComplex
{
Real = Real other.Real,
Imaginary = Imaginary other.Imaginary
};
return t;
}
public TComplex Subtract(TComplex other)
{
var t = new TComplex
{
Real = Real - other.Real,
Imaginary = Imaginary - other.Imaginary
};
return t;
}
public abstract TComplex Product(TComplex other);
#region Operators
public static TComplex operator (HyperComplexNumber<TComplex> rhs) => (TComplex)rhs;
public static TComplex operator -(HyperComplexNumber<TComplex> rhs) => rhs.Scale(-1);
public static TComplex operator (HyperComplexNumber<TComplex> lhs, TComplex rhs) => lhs.Add(rhs);
public static TComplex operator -(HyperComplexNumber<TComplex> lhs, TComplex rhs) => lhs.Subtract(rhs);
public static TComplex operator *(double factor, HyperComplexNumber<TComplex> lhs) => lhs.Scale(factor);
public static TComplex operator *(HyperComplexNumber<TComplex> lhs, double factor) => lhs.Scale(factor);
public static TComplex operator /(HyperComplexNumber<TComplex> lhs, double divider) => lhs.Scale(1/divider);
public static TComplex operator *(HyperComplexNumber<TComplex> lhs, TComplex rhs) => lhs.Product(rhs);
#endregion
#region IEquatable Members
/// <summary>
/// Equality overrides from <see cref="System.Object"/>
/// </summary>
/// <param name="obj">The object to compare this with</param>
/// <returns>False if object is a different type, otherwise it calls <code>Equals(HyperComplexNumber)</code></returns>
public override bool Equals(object obj)
{
if (obj is HyperComplexNumber<TComplex> item)
{
return Equals(item);
}
return false;
}
/// <summary>
/// Checks for equality among <see cref="HyperComplexNumber"/> classes
/// </summary>
/// <returns>True if equal</returns>
public bool Equals(HyperComplexNumber<TComplex> other)
{
return Real.Equals(other.Real)
&& Imaginary.Equals(other.Imaginary);
}
/// <summary>
/// Calculates the hash code for the <see cref="HyperComplexNumber"/>
/// </summary>
/// <returns>The int hash value</returns>
public override int GetHashCode()
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295) * hc Real.GetHashCode();
hc = (-1521134295) * hc Imaginary.GetHashCode();
return hc;
}
}
#endregion
public string ToString(string format, IFormatProvider formatProvider)
{
if (Real == 0 && Imaginary == 0) return "0";
string rPart = string.Empty, iPart = string.Empty, sign = string.Empty;
if (Real != 0.0)
{
rPart = Real.ToString(format, formatProvider);
}
if (Imaginary != 0.0)
{
if (Imaginary < 0)
{
sign = "-";
}
else if (Real != 0)
{
sign = " ";
}
iPart = $"{Math.Abs(Imaginary).ToString(format, formatProvider)}{Symbol}";
}
return $"{rPart}{sign}{iPart}";
}
public string ToString(string formatting)
=> ToString(formatting, null);
public override string ToString()
=> ToString("g");
}
public sealed class ComplexNumber :
HyperComplexNumber<ComplexNumber>,
IEquatable<ComplexNumber>
{
public ComplexNumber() : this(0, 0) { }
public ComplexNumber(double real) : this(real, 0) { }
public ComplexNumber(double real, double imaginary) : base(real, imaginary) { }
public static ComplexNumber Random() => new ComplexNumber(rng.NextDouble(), rng.NextDouble());
public static implicit operator ComplexNumber(double real) => new ComplexNumber(real);
public static explicit operator double(ComplexNumber number) => number.Real;
public override string Symbol => "i";
public override ComplexNumber Product(ComplexNumber other)
=> new ComplexNumber(
Real * other.Real - Imaginary * other.Imaginary,
Real * other.Imaginary Imaginary * other.Real);
public bool Equals(ComplexNumber other)
=> base.Equals(other);
}
public sealed class DualNumber :
HyperComplexNumber<DualNumber>,
IEquatable<DualNumber>
{
public DualNumber() : this(0, 0) { }
public DualNumber(double real) : this(real, 0) { }
public DualNumber(double real, double imaginary) : base(real, imaginary) { }
public static DualNumber Random() => new DualNumber(rng.NextDouble(), rng.NextDouble());
public static implicit operator DualNumber(double real) => new DualNumber(real);
public static explicit operator double(DualNumber number) => number.Real;
public override string Symbol => "ε";
public override DualNumber Product(DualNumber other)
=> new DualNumber(
Real * other.Real,
Real * other.Imaginary Imaginary * other.Real);
public bool Equals(DualNumber other)
=> base.Equals(other);
}
PS. I recommend using the new record
types which are like classes, but immutable and can auto-equate by value, something I have not implemented above.