Home > Enterprise >  Instantiate child class without knowing the type
Instantiate child class without knowing the type

Time:10-10

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:

  1. Might be used incorrectly (when operands are of different subtypes, like ComplexNumber and DualNumber).

  2. Has fixed return type of base HyperComplexNumber, so it's not convenient to use anyway. For example, if I have ComplexNumber a and ComplexNumber b, I'd expect in c = a b for c to be of type ComplexNumber at compile time, without any explicit casting. Now variable c will always be HyperComplexNumber (even though actual type might be ComplexNumber).

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.

  • Related