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

发布于 2024-09-25 12:43:39 字数 107 浏览 4 评论 0原文

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

I need to define an Interface which has to enforce certain operator overloading to the types which implements it. There doesn't seem an obvious way to do it since operator overloading has to be done using static methods in class. Is there any way to achieve the same effect (using abstract classes or anything else)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

み青杉依旧 2024-10-02 12:43:39

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

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

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

    protected abstract MyClass __DoAddition(MyClass c2);
}

Bit of a hack, but...

You could provide operator overloads in your base class that then call some published abstract methods in one of the classes to do the job there.

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

    protected abstract MyClass __DoAddition(MyClass c2);
}
橘寄 2024-10-02 12:43:39

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

另一种方法是放弃操作符并使用基于接口的方法;例如,如果您需要 T 具有 +(T,T),则使用具有 Add(T) 方法的接口代替运算符。这里的另一个优点是接口可以从泛型中使用(通常通过约束),而使用泛型代码中的运算符需要一些努力。

No. The only sensible way to do this would be to have a unit test check use reflection to find all concrete implementations, and then verify this condition. You could also perhaps do something at runtime re the same via a static constructor, but then the question is which static constructor?

Another approach is to drop the operators and use an interface-based approach; for example , if you need T to have +(T,T) then instead of operators have an interface with an Add(T) method. Another advantage here is that interfaces are usable from generics (typically via constraints), where-as using operators from generic code takes some effort.

凶凌 2024-10-02 12:43:39

您可以在抽象基类中实现重载,但将实际操作细节委托给抽象方法。然后必须实现这一点,并且重载将通过它们的实现来完成。

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

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

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

You could implement the overloading in an abstract base class, but delegate the actually operation specifics to an abstract method. Then this will have to be implemented and the overloading will be don with their implementation.

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

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

Though I'm not sure if this is complete.

多像笑话 2024-10-02 12:43:39

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

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

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

现在,假设您引用接口,那么是的,这是可能的!诀窍是不要在接口或类上定义运算符,而是在包装器struct中定义 - 它通过隐式运算符的魔力使用< /code>...

虽然有一点问题,但是如果您使用的是 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)。
    • 包装结构具有静态运算符方法,它们都接受并返回相同的Operable
    • 但最重要的是:struct Operaable 还定义了隐式运算符,用于在 TImplTValue 之间进行隐式转换,这有助于使这种方法可用。
    • TValue 进行隐式转换是不可能的,因为无法知道 TImpl 是什么,但您可以在 TValue 上定义运算符code>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,因此此实现定义了 复数

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,因此将调用重载的 + 运算符。

快速而肮脏的解决方法是在最里面的操作数上使用该Op属性(根据运算符优先级规则)来触发到 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 );
}

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

...然后可以处理任何长度和复杂性的表达式,只需记住在第一个操作上使用 .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 代码分析类型,用于验证任何实现 IPerable 的类型也重载了运算符。
    • 这更像是对您的原始问题的解决方案:一种“强制”实现接口的类型也重载运算符的方法。
    • 但这并不完美:因为外部程序集中的类型仍然可以合法编译,而无需重载运算符。尽管至少使用struct Operand,您仍然可以利用重载运算符(尽管带有.Op疣)
  2. 使用Roslyn代码生成在a中自动生成必要的运算符其他地方提供的 partial 类型,包括自动生成隐式运算符,以适应任何未重载运算符的外部类型。

第 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 部分...现在对我来说太费力了,所以请考虑将其作为读者的练习。

Is there any way in C# to enforce operator overloading in derived classes?

Strictly speaking, types don't "derive" from an interface, they only implement it - but if you are referring to requiring operator overloads in a derived class from a parent class then that can be done by making the parent class generic to allow for variant operator parameter and return types, but this effectively means that the subclass cannot then be subclassed again (without making the subclass generic).

Is there any way to achieve the same effect (using abstract classes or anything else)?

Now, assuming that you are referring to only interfaces, then yes, it's possible! The trick is to not have the operators defined on the interface nor it classes, but in a wrapper-struct - which is used through the magic of operator implicit...

There is a slight catch though, but which is entirely solvable provided you're using C# 6.0 or later... read on!


  • What you're describing is (another) good use-case for wrapper-structs over open generic interfaces.

    • This is basically the same thing as Mads Torgersen's suggested approach for implementing "extension-everything": where any object (class) or value (struct), including any interface, is wrapped in a struct invisibly to your program, and that wrapper-struct then adds the required functionality you're after.
    • ...in this case, you want to add "extension operators" to types that will implement an existing interface that defines the underling operations for those operators.
    • Remember that value-types (struct etc) are "free" in .NET because they don't incur a GC heap allocation.
  • To make this work, first define the interface with the operations you want to support.

    • This interface has generic type parameters to allow for variant return types, and for accessing the "raw value":
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();
}
  • Then define the wrapper-struct (struct Operable<TImpl,TValue>).
    • The wrapper-struct has the static operator methods which all accept and return the the same Operable<TImpl,TValue>.
    • But most importantly: struct Operable also has implicit operator defined for implicit conversion to-and-from TImpl and to TValue, which is what helps make this approach usable.
    • Implicit conversion from TValue isn't possible because there's no way of knowing what TImpl would be, but you can define operators on struct Operable<> that allow raw TValue for either of the operands, that way it can infer TImpl from the other operand.
// 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; }
}

So for example, supposing we have a custom number type that we want to enforce at compile-time the operators described above...

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

Simply make it implement IOperable, so this implementation defines most arithmetic operations on 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
}

So this should be able to be used like so:

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

...but it doesn't!

The problem is that we need either a or b to be implicitly promoted to Operable<ComplexNumber,ComplexNumber> so that the overloaded + operator will be invoked.

The quick-and-dirty workaround is to use that Op property on the innermost operand (according to operator precedence rules) to trigger the implicit conversion to Operable<> and the compiler takes care of the rest, including implicit conversion back to ComplexNumber:

So this:

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

...gives me the expected output of (6+4i) + (8--2i) = (14+2i).

...which then works with expressions of any length and complexity, just remember to use .Op on the first operation, not the left-most (in this case, both b.Op and d.Op due to them being independent operations:

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

Of course, the .Op part is still an ugly wart, but what can be done about that?

Well, the answer lies in two parts:

  1. A Roslyn code analysis type that verifies that any type that implements IOperable also has the operators overloaded.
    • This is more of a solution to your original question: a way to "enforce" that a type that implements an interface also overloads the operators.
    • This isn't perfect, though: as a type in an external assembly could still be legally compiled without the operators overloaded. Though at least with struct Operand you can still take advantage of overloaded operators (albeit with the .Op wart)
  2. Use Roslyn code-generation to auto-generate the necessary operators in a partial type provided elsewhere, including auto-generating operator implicit to accommodate any external types that haven't overloaded the operators.

Part 1 is straightforward, here's a simple Roslyn analyzer that will raise a warning (or error, at your discretion):

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

Just copy and paste the above into the stock Roslyn analyzer project in VS's project templates and off y'go.


Part 2... is too much effort for me now, so consider that an exercise for the reader.

眼泪淡了忧伤 2024-10-02 12:43:39

我过去曾经这样做过......

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

I have done this in the past...

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

And then implemented as follows:

public class ConcreteClass : BaseClass<ConcreteClass>
{
    protected ConcreteClass DoAddition(ConcreteClass c2)
    {
        ...
    }
}
夜吻♂芭芘 2024-10-02 12:43:39

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

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

Since its operator can only be overloaded and not overridden its quite difficult. The best solution I can think of is use an abstract class and overloading like this.

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);
    }
}
茶花眉 2024-10-02 12:43:39

我会做类似的事情:

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;

I'd do something like:

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

Then you can inherit Scalar as:

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

    ...
}

And that's it:

Rational a = ...;
Rational b = ...;
Rational sum = a + b;
椒妓 2024-10-02 12:43:39

由于写了很多答案,似乎发生了一些变化。到 2023 年,您可以使用接口强制运算符重载,而无需使用任何解决方法。
让我们定义接口:

public interface IOverloading<T> where T: IOverloading<T>
{
    static abstract T operator +(T a, T i);
}

现在让我们实现接口的类:

public class Overloaded : IOverloading<Overloaded>
{
    public int Value {get; private set; }

    public Overloaded(int i) {
        Value = i;
    }

    public static Overloaded operator +(Overloaded a, Overloaded b){
        return new Overloaded(a.Value + b.Value);
    }

    
    static void Main(string[] args)
    {
        Overloaded x = new Overloaded(13);
        Overloaded y = new Overloaded(12);
        Overloaded result = x + y;
        Console.WriteLine("x(13) + y(12): {0}", result.Value);
    }
}

当我们运行程序时,我们会在控制台上得到以下输出:

> x(13) + y(12): 25

There seem to have been some changes since a lot of the answers have been written. In 2023 you can use interfaces to force operator overloading without using any workarounds.
Lets define the interface:

public interface IOverloading<T> where T: IOverloading<T>
{
    static abstract T operator +(T a, T i);
}

Now lets do the class implementing the interface:

public class Overloaded : IOverloading<Overloaded>
{
    public int Value {get; private set; }

    public Overloaded(int i) {
        Value = i;
    }

    public static Overloaded operator +(Overloaded a, Overloaded b){
        return new Overloaded(a.Value + b.Value);
    }

    
    static void Main(string[] args)
    {
        Overloaded x = new Overloaded(13);
        Overloaded y = new Overloaded(12);
        Overloaded result = x + y;
        Console.WriteLine("x(13) + y(12): {0}", result.Value);
    }
}

When we run the program we get the following output on the console:

> x(13) + y(12): 25
预谋 2024-10-02 12:43:39

从 C# 11 开始有

IAdditionOperators<TSelf, TOther, TResult>

ISubtractionOperators<TSelf, TOther, TResult>

Since C# 11 there is

IAdditionOperators<TSelf, TOther, TResult>

ISubtractionOperators<TSelf, TOther, TResult>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文