在C#中是否有任何方法可以在派生类中强制执行运算符重载?

时间:2010-09-28 09:16:07

标签: c# interface operator-overloading abstract-class

我需要定义一个接口,它必须强制执行某些运算符重载到实现它的类型。似乎没有一种明显的方法可以做到这一点,因为运算符重载必须在类中使用静态方法完成。有没有办法达到相同的效果(使用抽象类或其他任何东西)?

7 个答案:

答案 0 :(得分:23)

有点黑客,但是......

您可以在基类中提供运算符重载,然后在其中一个类中调用一些已发布的抽象方法来完成该任务。

public abstract class MyClass
{
    public static MyClass operator +(MyClass c1, MyClass c2) 
    {
        return c1.__DoAddition(c2);
    }

    protected abstract MyClass __DoAddition(MyClass c2);
}

答案 1 :(得分:8)

没有。唯一明智的方法是使用单元测试检查使用反射来查找所有具体实现,然后验证这种情况。您也可以 在运行时通过静态构造函数执行相同操作,但问题是哪个静态构造函数?

另一种方法是删除运算符并使用基于接口的方法;例如,如果您需要T拥有+(T,T),则代替运算符的接口使用Add(T)方法。这里的另一个优点是接口可用于泛型(通常通过约束),其中 - 使用来自通用代码的运算符需要付出一些努力。

答案 2 :(得分:5)

您可以在抽象基类中实现重载,但是将实际操作细节委托给抽象方法。然后,这将必须实施,重载将与他们的实现相结合。

public abstract class OverLoadingBase
{
    public abstract OverLoadingBase DoAdd(OverLoadingBase y);

    public static OverLoadingBase operator +(OverLoadingBase x, OverLoadingBase y)
    {
        return x.DoAdd(y);
    }    
}

虽然我不确定这是否完整。

答案 3 :(得分:4)

我过去做过这个......

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

然后实施如下:

public class ConcreteClass : BaseClass<ConcreteClass>
{
    protected ConcreteClass DoAddition(ConcreteClass c2)
    {
        ...
    }
}

答案 4 :(得分:1)

由于它的操作符只能被重载而不能被覆盖,因此非常困难。我能想到的最好的解决方案是使用抽象类并像这样重载。

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

答案 5 :(得分:0)

我会做类似的事情:

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

然后,您可以将Scalar继承为:

public sealed class Rational : Scalar<Rational>
{
    public override Rational Add(Rational another)
    {
      ...
    }

    ...
}

就是这样:

Rational a = ...;
Rational b = ...;
Rational sum = a + b;

答案 6 :(得分:0)

<块引用>

在 C# 中是否有任何方法可以在派生类中强制执行运算符重载?

严格来说,类型不是从 interface“派生”的,它们只是 implement 它 - 但是如果您指的是在派生的 class 中要求运算符重载从父 class 然后可以通过使父 class 泛型以允许变体运算符参数和返回类型来完成,但这实际上意味着子类不能再次被子类化(不使子类通用)。

<块引用>

有没有办法达到同样的效果(使用抽象类或其他任何东西)?

现在,假设您指的是接口,那么,这是可能的!诀窍是不要在接口或类上定义运算符,而是在包装器-struct 中定义运算符 - 通过 operator implicit...< /p>

虽然有一个小问题,但是如果您使用的是 C# 6.0 或更高版本,这是 完全可以解决的...继续阅读!


  • 您所描述的是(另一个)很好的用例,适用于开放通用接口上的包装器struct

    • 这与 Mads Torgersen 建议的实现“扩展一切”的方法基本相同:其中任何对象 (class) 或值 (struct),包括任何 interface,包装在 struct 中,对您的程序不可见,然后该包装结构会添加您需要的功能。
    • ...在这种情况下,您希望将“扩展运算符”添加到将实现现有接口的类型中,该接口定义这些运算符的底层操作。
    • 请记住,值类型(struct 等)在 .NET 中是“免费的”,因为它们不会导致 GC 堆分配。
  • 要完成这项工作,首先要定义带有您要支持的操作的接口。

    • 该接口具有泛型类型参数以允许变体返回类型和访问“原始值”:
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();
}
  • 然后定义包装结构(struct Operable<TImpl,TValue>)。
    • 包装结构具有 static operator 方法,它们都接受并返回相同的 Operable<TImpl,TValue>
    • 但最重要的是:struct Operable 还定义了 implicit operator 用于隐式转换 to-and-from TImpl 和 to TValue,这有助于使这种方法可用。
    • 隐式转换 from TValue 是不可能的,因为无法知道 TImpl 是什么,但是您可以在 struct Operable<> 上定义运算符,允许任何一个操作数的原始 TValue,这样它就可以从另一个操作数推断 TImpl
// 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; }
}

例如,假设我们有一个自定义数字类型,我们希望在编译时强制执行上述运算符...

public struct ComplexNumber
{
    public Double Real;
    public Double Complex;
}

简单地让它实现IOperable,所以这个实现定义了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
}

所以这应该可以像这样使用:

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

...但它没有!

问题是我们需要将 ab 隐式提升Operable<ComplexNumber,ComplexNumber> 以便重载的 + 运算符将是调用。

quick-and-dirty 解决方法是使用最内层操作数上的 Op 属性(根据 operator precedence rules)触发隐式转换为 {{1} } 并且编译器负责其余的工作,包括隐式转换回 Operable<>:

所以:

ComplexNumber

...给了我 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 ); } 的预期输出。

...然后可以处理任何长度和复杂度的表达式,只需记住在 first 操作上使用 (6+4i) + (8--2i) = (14+2i),而不是最左边的(在这种情况下,两个 { {1}} 和 .Op,因为它们是独立的操作:

b.Op

当然,d.Op 部分仍然是一个丑陋的疣,但有什么办法呢?

嗯,答案分为两部分:

  1. 一种 Roslyn 代码分析类型,用于验证实现 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 ); } 的任何类型是否也重载了运算符。
    • 这更像是对您的原始问题的解决方案:一种“强制”实现接口的类型也会重载运算符的方法。
    • 但这并不完美:作为外部程序集中的类型仍然可以合法编译运算符重载。尽管至少使用 .Op 您仍然可以利用重载运算符(尽管带有 IOperable 疣)
  2. 使用 Roslyn 代码生成以其他地方提供的 struct Operand 类型自动生成必要的运算符,包括自动生成 .Op 以适应没有重载运算符的任何外部类型。

第 1 部分很简单,这是一个简单的 Roslyn 分析器,它会发出警告(或错误,由您自行决定):

partial

只需将上述内容复制并粘贴到 VS 项目模板中的 Roslyn 分析器项目中即可。


第 2 部分......现在对我来说太费力了,所以把它当作读者的练习。