【问题标题】:Is there any way in C# to enforce operator overloading in derived classes?C# 中有什么方法可以在派生类中强制运算符重载吗?
【发布时间】:2011-04-18 04:45:26
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    在 C# 中有什么方法可以强制派生类中的运算符重载?

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

    有什么方法可以达到同样的效果(使用抽象类或其他任何东西)?

    现在,假设您指的是接口,那么是的,这是可能的!诀窍是在接口或它的类上定义运算符,而是在包装器中-struct - 通过operator implicit 的魔力使用...

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


    • 您所描述的是 wrapper-structs 在开放通用接口上的(另一个)很好的用例。

      • 这与 Mads Torgersen 建议的实现“extension-everything”的方法基本相同:任何对象 (class) 或值 (struct),包括任何 interface,都包含在 @987654334 中@ 对您的程序不可见,然后该 wrapper-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&lt;TImpl,TValue&gt;)。
      • 包装结构具有static operator 方法,它们都接受并返回相同的Operable&lt;TImpl,TValue&gt;
      • 但最重要的是:struct Operable 还定义了 implicit operator,用于与 TImplTValue 的隐式转换,这有助于使这种方法可用。
      • 隐式转换来自 TValue 是不可能的,因为无法知道TImpl 是什么,但您可以在struct Operable&lt;&gt; 上定义运算符,允许原始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&lt;ComplexNumber,ComplexNumber&gt;,以便调用重载的+ 运算符。

    quick-and-dirty 解决方法是在最里面的操作数(根据operator precedence rules)上使用Op 属性来触发到Operable&lt;&gt; 的隐式转换,编译器会小心其余的,包括隐式转换回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 );
    }
    

    ...给我(6+4i) + (8--2i) = (14+2i) 的预期输出。

    ...然后适用于任何长度和复杂度的表达式,只要记住在 first 操作上使用.Op,而不是最左边(在这种情况下,b.Opd.Op,因为它们是独立的操作:

    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 部分仍然是一个丑陋的疣,但是可以做些什么呢?

    嗯,答案分为两部分:

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

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

    using System;
    using System.Collections.Generic;
    using System.Collections.Immutable;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.Diagnostics;
    
    namespace You
    {
        [DiagnosticAnalyzer( LanguageNames.CSharp )]
        public class OperatorOverloadingAnalyzer : DiagnosticAnalyzer
        {
            public static ImmutableArray<String> FixableDiagnosticIds { get; } = ImmutableArray.Create( "0xDEADBEEF0001" );
    
            public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => OperatorOverloadingAnalyzerInfo.AsArray;
    
            public override void Initialize( AnalysisContext context )
            {
                context.ConfigureGeneratedCodeAnalysis( GeneratedCodeAnalysisFlags.None );
                context.EnableConcurrentExecution();
    
                context.RegisterSymbolAction( AnalyzeSymbol, SymbolKind.NamedType );
            }
    
            private static void AnalyzeSymbol( SymbolAnalysisContext context )
            {
                if( IsClassOrStructThatImplementsIOperable( context.Symbol, out INamedTypeSymbol? namedTypeSymbol ) )
                {
                    if( !HasOperators( namedTypeSymbol ) )
                    {
                        Diagnostic diagnostic = Diagnostic.Create( OperatorOverloadingAnalyzerInfo.Descriptor, location: namedTypeSymbol.Locations[0], namedTypeSymbol.Name );
    
                        context.ReportDiagnostic( diagnostic );
                    }
                }
            }
    
            private static Boolean IsClassOrStructThatImplementsIOperable( ISymbol symbol, [NotNullWhen(true)] out INamedTypeSymbol? namedTypeSymbol )
            {
                if( symbol is INamedTypeSymbol ins )
                {
                    namedTypeSymbol = ins;
    
                    if( namedTypeSymbol.TypeKind == TypeKind.Class || namedTypeSymbol.TypeKind == TypeKind.Struct )
                    {
                        if( namedTypeSymbol.AllInterfaces.Any( iface => iface.Name == "IOperable" ) )
                        {
                            return true;
                        }
                    }
    
                    return false;
                }
                else
                {
                    namedTypeSymbol = null;
                    return false;
                }
            }
    
            private static readonly HashSet<String> _opMemberNames = new HashSet<String>( StringComparer.Ordinal )
            {
                "op_Addition",
                "op_Division",
                "op_Multiply",
                "op_Subtraction"
            };
    
            private static Boolean HasOperators( INamedTypeSymbol type )
            {
                Int32 matchCount = 0;
                foreach( String memberName in type.MemberNames )
                {
                    if( _opMemberNames.Contains( memberName ) )
                    {
                        matchCount++;
                    }
                }
    
                return matchCount == 4;
            }
        }
    }
    

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


    第 2 部分...现在对我来说太费力了,因此请考虑将其作为读者练习。

    【讨论】:

      【解决方案2】:

      我会这样做:

      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;
      

      【讨论】:

        【解决方案3】:

        我过去也这样做过……

        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】:

          您可以在抽象基类中实现重载,但将实际操作细节委托给抽象方法。然后这将必须被实现,并且重载将随着它们的实现而被取消。

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

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

          【讨论】:

            【解决方案5】:

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

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

            【讨论】:

              【解决方案6】:

              有点小题大做,但是...

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

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

              【讨论】:

              • 不好,但有效。我也是这样做的。
              • 这个问题是返回类型是MyClass,而不是覆盖类。这使得链接相当冒险,而链接是这里的意图。
              • @Marc, @Code:返回一个接口可能更好?例如IMyClass 强制实现 DoAddition(),因此将其公开
              • @abatishchev:但是返回一个接口将再次阻止链接,正如 Marc 所提到的,这是必不可少的!
              • 您还可以使用具有泛型类必须从其派生的约束的泛型抽象类。例如。 public abstract class SomeClass&lt;T&gt; where T : SomeClass&lt;T&gt; 然后让所有方法都将类型 T 作为参数,并为其返回类型。
              【解决方案7】:

              没有。唯一明智的做法是让单元测试检查使用反射来查找所有具体实现,然后验证此条件。您也可以通过静态构造函数在运行时做同样的事情,但问题是哪个静态构造函数?

              另一种方法是删除运算符并使用基于接口的方法;例如,如果您需要 T 来拥有 +(T,T) ,那么操作员应该有一个带有 Add(T) 方法的接口。这里的另一个优点是接口可以从泛型中使用(通常通过约束),而在泛型代码中使用运算符需要一些努力。

              【讨论】:

              • 使用加、减等方法是下一个显而易见的选择,但因为我的代码将有很多计算,如s = s1 * 2 + s2 * s3 - s4 * (s5 - s6);。使用方法会使其成为s = s1.Multiply(2).Add(s2.Multiply(s3)).Subtract(s4.Multiply(s5.Subtract(s6)));当然不是很可读!
              • @Hemant:您也可以使用撇号`将代码高亮显示到 cmets 中
              • @MarcGravell 类似的问题。用于 Model 和 ViewModel 类之间类型转换的隐式运算符,例如此示例 MVVM Mapping via Implicit Operators,但重构为基类或接口或 ViewModel 类强加于它们的东西。想法?
              • @one.beat.consumer 我的主要想法:使用接口
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-08-06
              • 2020-12-18
              • 1970-01-01
              • 1970-01-01
              • 2021-07-02
              • 2012-06-02
              相关资源
              最近更新 更多