Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Abstract Class Operator Overload

I have an abstract class, Vector, which I would like to overload the operators +,-,*, etc.
I want any derived classes to be able to use these, and get an object back with the same type as the calling object.
I tried with generics, (as follows, in brief), but I couldn't find a legal way to do it:

public static T operator +<T>( T V1, T V2) where T : Vector
{
     //some calculation
     return new T(args);
}

I then tried to do it just using the base class:

    public static Vector operator+(Vector V1, Vector V2)
    {
        if (V1.Dimension != V2.Dimension)
            throw new VectorTypeException("Vector Dimensions Must Be Equal");
        double[] ArgList = new double[V1.Dimension];
        for (int i = 0; i < V1.Dimension; i++) { ArgList[i] = V1[i] + V2[i]; }

        return (Vector)Activator.CreateInstance(V1.GetType(), new object[] { ArgList});
    }

If this method is passed in two child objects, it should perform the operation on them, and return a new object of the same heritage.

The problem I ran into with this is that I cannot enforce that all such child classes must have a constructor with the appropriate signature, and I can't call the base constructor to make the object.

What are ways to either (a) Make either of these work, or (b) do this elegantly in another way?

like image 645
3Pi Avatar asked Jun 13 '12 22:06

3Pi


1 Answers

You could declare instance-level abstract methods which your subclass can override:

public abstract class Vector
{
    protected abstract Vector Add(Vector otherVector);

    public static Vector operator +(Vector v1, Vector v2)
    {
        return v1.Add(v2);
    }
}

public class SubVector : Vector
{
    protected override Vector Add(Vector otherVector)
    {
        //do some SubVector addition
    }
}

Might run into some issues especially with multiple subclasses (Will SubVector have to know how to add with SomeOtherSubVectorClass? What if you add ThirdVectorType class?) and perhaps handling null cases. Also, making sure that SubVector.Add behaves the same as SomeOtherSubVectorClass.Add when it comes to commutative operations.

EDIT: based on your other comments, you could so something like:

public class Vector2D : Vector
{
    public double X { get; set; }
    public double Y { get; set; }

    protected override Vector Add(Vector otherVector)
    {
        Vector2D otherVector2D = otherVector as Vector2D;
        if (otherVector2D != null)
            return new Vector2D() { X = this.X + otherVector2D.X, Y = this.Y + otherVector2D.Y };

        Vector3D otherVector3D = otherVector as Vector3D;
        if (otherVector3D != null)
            return new Vector3D() { X = this.X + otherVector3D.X, Y = this.Y + otherVector3D.Y, Z = otherVector3D.Z };

        //handle other cases
    }
}


public class Vector3D : Vector
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }

    protected override Vector Add(Vector otherVector)
    {
        Vector2D otherVector2D = otherVector as Vector2D;
        if (otherVector2D != null)
            return new Vector3D() { X = this.X + otherVector2D.X, Y = this.Y + otherVector2D.Y, Z = this.Z };

        Vector3D otherVector3D = otherVector as Vector3D;
        if (otherVector3D != null)
            return new Vector3D() { X = this.X + otherVector3D.X, Y = this.Y + otherVector3D.Y, Z = this.Z + otherVector3D.Z };

        //handle other cases
    }
}

EDITx2:

Given your latest comment, perhaps your should just maintain an internal array/matrix and just do generic matrix math. Your subclasses can expose X/Y/Z property wrappers against the array indicies:

public class Vector
{
    protected double[] Values;
    public int Length { get { return Values.Length; } }

    public static Vector operator +(Vector v1, Vector v2)
    {
        if (v1.Length != v2.Length)
        {
            throw new VectorTypeException("Vector Dimensions Must Be Equal");
        }
        else
        {
            //perform generic matrix addition/operation
            double[] newValues = new double[v1.Length];
            for (int i = 0; i < v1.Length; i++)
            {
                newValues[i] = v1.Values[i] + v2.Values[i];
            }

            //or use some factory/service to give you a Vector2D, Vector3D, or VectorND
            return new Vector() { Values = newValues };
        }
    }
}

public class Vector2D : Vector
{
    public double X
    {
        get { return Values[0]; }
        set { Values[0] = value; }
    }
    public double Y
    {
        get { return Values[1]; }
        set { Values[1] = value; }
    }
}


public class Vector3D : Vector
{
    public double X
    {
        get { return Values[0]; }
        set { Values[0] = value; }
    }
    public double Y
    {
        get { return Values[1]; }
        set { Values[1] = value; }
    }
    public double Z
    {
        get { return Values[2]; }
        set { Values[2] = value; }
    }
}

EDITx3: Based on your latest comment, I guess you could implement operator overloads on each subclass, do the shared logic in a static method (say in the base Vector class), and somewhere do a switch/case check to provide a specific subclass:

    private static Vector Add(Vector v1, Vector v2)
    {
        if (v1.Length != v2.Length)
        {
            throw new VectorTypeException("Vector Dimensions Must Be Equal");
        }
        else
        {
            //perform generic matrix addition/operation
            double[] newValues = new double[v1.Length];
            for (int i = 0; i < v1.Length; i++)
            {
                newValues[i] = v1.Values[i] + v2.Values[i];
            }

            //or use some factory/service to give you a Vector2D, Vector3D, or VectorND
            switch (newValues.Length)
            {
                case 1 :
                    return new Vector1D() { Values = newValues };
                case 2 :
                    return new Vector2D() { Values = newValues };
                case 3 :
                    return new Vector3D() { Values = newValues };
                case 4 :
                    return new Vector4D() { Values = newValues };
                //... and so on
                default :
                    throw new DimensionOutOfRangeException("Do not support vectors greater than 10 dimensions");
                    //or you could just return the generic Vector which doesn't expose X,Y,Z values?
            }
        }
    }

Then your subclasses would have:

    public class Vector2D
    {
        public static Vector2D operator +(Vector2D v1, Vector2D v2)
        {
            return (Vector2D)Add(v1, v2);
        }
    }

    public class Vector3D
    {
        public static Vector3D operator +(Vector3D v1, Vector3D v2)
        {
            return (Vector3D)Add(v1, v2);
        }
    }

Some duplication, but I don't see a way around it off the top of my head to allow the compiler to do this:

    Vector3 v1 = new Vector3(2, 2, 2);
    Vector3 v2 = new Vector3(1, 1, 1);
    var v3 = v1 + v2; //Vector3(3, 3, 3);
    Console.WriteLine(v3.X + ", " + v3.Y + ", " + v3.Z);

or for other dimensions:

    Vector2 v1 = new Vector2(2, 2);
    Vector2 v2 = new Vector2(1, 1);
    var v3 = v1 + v2; //Vector2(3, 3, 3);
    Console.WriteLine(v3.X + ", " + v3.Y); // no "Z" property to output!
like image 124
Chris Sinclair Avatar answered Sep 23 '22 02:09

Chris Sinclair