Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 925
Hemant Avatar asked Sep 28 '10 09:09

Hemant


People also ask

Does New exist in C?

There's no new / delete expression in C. The closest equivalent are the malloc and free functions, if you ignore the constructors/destructors and type safety.

Does C use this?

In C you do not have the this keyword. Only in C++ and in a class, so your code is C and you use your this variable as a local method parameter, where you access the array struct.


7 Answers

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);
}
like image 112
Codesleuth Avatar answered Oct 16 '22 20:10

Codesleuth


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.

like image 30
Marc Gravell Avatar answered Oct 16 '22 20:10

Marc Gravell


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.

like image 44
Preet Sangha Avatar answered Oct 16 '22 20:10

Preet Sangha


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)
    {
        ...
    }
}
like image 35
Hubris Avatar answered Oct 16 '22 21:10

Hubris


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.

like image 29
Dai Avatar answered Oct 16 '22 21:10

Dai


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);
    }
}
like image 28
Bear Monkey Avatar answered Oct 16 '22 21:10

Bear Monkey


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;
like image 23
hdkrus Avatar answered Oct 16 '22 20:10

hdkrus