C#抽象类的运算符重载

11

我有一个抽象类 Vector,我想重载+、-、*等运算符。我希望任何派生类都能够使用它们,并返回与调用对象相同类型的对象。
我尝试使用泛型(简要如下),但我找不到一种合法的方法来实现:

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

我随后尝试只使用基类进行操作:

    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});
    }

如果这个方法传入两个子对象,它应该对它们执行操作,并返回一个相同继承的新对象。我遇到的问题是,我无法强制所有这样的子类都必须具有适当签名的构造函数,也不能调用基础构造函数来创建对象。
有哪些方法可以使其中任何一个方法起作用,或以另一种优雅的方式实现这个方法?

你的派生类是什么样子的? - JotaBe
假设VectorAVectorB都派生自Vector,那么VectorA + VectorB的结果会是什么? - Austin Salonen
对我来说,你需要子类化Vector(这似乎主要是一个double数组)的需求有些奇怪。您能否更详细地解释一下您的层次结构? - dlev
1
你能否实现类似 protected abstract Vector Add(Vector otherVector) 方法?这样你的运算符就可以在其中一个上调用虚拟方法,并允许子实现处理工作。 - Chris Sinclair
1
附注:在 + 运算符中进行反射会导致代码性能非常不直观。请考虑其他方法或非常聪明地缓存反射以避免令人惊讶的性能损失。 - Alexei Levenkov
显示剩余4条评论
3个回答

13
您可以声明实例级别的抽象方法,您的子类可以重写它:
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
    }
}

在处理多个子类(例如,SubVector 是否需要知道如何与SomeOtherSubVectorClass相加?如果添加了ThirdVectorType类,会怎么样?)和处理 null 情况时可能会遇到一些问题。此外,要确保在进行可交换操作时,SubVector.Add 的行为与SomeOtherSubVectorClass.Add 相同。
编辑:根据您的其他评论,您可以进行以下操作:
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
    }
}

编辑x2:

根据您最新的评论,也许您只需要维护一个内部数组/矩阵,并进行通用矩阵数学运算。您的子类可以针对数组索引公开X/Y/Z属性包装器:

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:根据您最新的评论,我猜想您可以在每个子类上实现运算符重载,在静态方法中处理共享逻辑(例如在基础向量类中),并在某个地方进行switch/case检查以提供特定的子类:
    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?
            }
        }
    }

那么你的子类将会有:

    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);
        }
    }

有一些重复,但我暂时想不出让编译器能够解决这个问题的方法:

    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);

或者对于其他维度:
    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!

在我的情况下,只有长度相同的向量才能相互相加。我本质上是在尝试实现正确的向量行为。但由于所有维度的向量都以相同的方式添加,只是具有不同数量的参数,因此我希望只编写一次代码。 - 3Pi
啊,我在这条评论之后进行了编辑。也许你应该使用索引数组来处理向量?进行一些长度检查,并根据需要以通用方式执行矩阵运算? - Chris Sinclair
这基本上就是我已经有的,并且已经发布了。我感到困惑的地方在于强制转换/新对象创建发生的部分。如果返回的对象被创建为子类,但作为父类传递出去,它能够返回给子类吗? - 3Pi
请查看我上面的最后一次编辑。代码有些重复,并且基类中有一个奇怪的switch/case。可以考虑将该switch/case移动到专用工厂中。无论如何,除此之外,不确定还有什么其他方法可以保持向您的向量消费者的隐式类型。 - Chris Sinclair

0

考虑使用一个名为Add()的抽象方法,让operator+作为其包装器。例如,"return v1.Add(v2)"。这还可以使您定义接口,非Vector类可以将其代码限制为该接口,从而能够执行类似于数学运算的操作(因为通用代码无法看到/触摸任何类型的+、-等运算符)。

在通用方法中,您只能使用默认(即无参数)构造函数进行编码,必须在方法/类型的通用约束中指定它。


0
五年后,我遇到了完全相同的问题,只是我称它们为N元组而不是向量。这是我所做的:
using System;
using System.Collections.Generic;

  public class Ntuple{
    /*parent class
    has an array of coordinates
    coordinate-wise addition method
    greater or less than in dictionary order
    */
    public List<double> Coords = new List<double>();
    public int Dimension;

    public Ntuple(List<double> Input){
      Coords=Input;
      Dimension=Input.Count;
    }//instance constructor

    public Ntuple(){
    }//empty constructor, because something with the + overload?


   public static Ntuple operator +(Ntuple t1, Ntuple t2)
   {
     //if dimensions don't match, throw error
     List<double> temp = new List<double>();
     for (int i=0; i<t1.Dimension; i++){
       temp.Add(t1.Coords[i]+t2.Coords[i]);
     }
     Ntuple sum = new Ntuple(temp);
     return sum;
   }//operator overload +

   public static bool operator >(Ntuple one, Ntuple other){
     //dictionary order
     for (int i=0; i<one.Dimension; i++){
       if (one.Coords[i]>other.Coords[i]) {return true;}
     }
     return false;
   }
   public static bool operator <(Ntuple one, Ntuple other){
     //dictionary order
     for (int i=0; i<one.Dimension; i++){
       if (one.Coords[i]<other.Coords[i]) {return true;}
     }
     return false;
   }

  }//ntuple parent class



  public class OrderedPair: Ntuple{
    /*
    has additional method PolarCoords, &c
    */
    public OrderedPair(List<double> Coords) : base(Coords){}
    //instance constructor
    public OrderedPair(Ntuple toCopy){
      this.Coords=toCopy.Coords;
      this.Dimension=toCopy.Dimension;
    }

  }//orderedpair

  public class TestProgram{
    public static void Main(){
      List<double> oneCoords=new List<double>(){1,2};
      List<double> otherCoords= new List<double>(){2,3};


      OrderedPair one = new OrderedPair(oneCoords);
      OrderedPair another = new OrderedPair(otherCoords);
      OrderedPair sum1 = new OrderedPair(one + another);


      Console.WriteLine(one.Coords[0].ToString()+one.Coords[1].ToString());
      Console.WriteLine(sum1.Coords[0].ToString()+sum1.Coords[1].ToString());

      bool test = one > another;
      Console.WriteLine(test);
      bool test2 = one < another;
      Console.WriteLine(test2);
    }
  }


}//namespace ntuples

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接