【发布时间】:2011-04-18 04:45:26
【问题描述】:
我需要定义一个接口,它必须对实现它的类型强制执行某些运算符重载。似乎没有明显的方法可以做到这一点,因为必须使用类中的静态方法来完成运算符重载。有什么方法可以达到同样的效果(使用抽象类或其他任何东西)?
【问题讨论】:
标签: c# interface operator-overloading abstract-class
我需要定义一个接口,它必须对实现它的类型强制执行某些运算符重载。似乎没有明显的方法可以做到这一点,因为必须使用类中的静态方法来完成运算符重载。有什么方法可以达到同样的效果(使用抽象类或其他任何东西)?
【问题讨论】:
标签: c# interface operator-overloading abstract-class
在 C# 中有什么方法可以强制派生类中的运算符重载?
严格来说,类型不会“派生”自 interface,它们只是 implement - 但如果您指的是从父 class 派生的 class 中要求运算符重载那么这可以通过使父 class 泛型以允许变体运算符参数和返回类型来完成,但这实际上意味着子类不能再被子类化(不使子类泛型)。
有什么方法可以达到同样的效果(使用抽象类或其他任何东西)?
现在,假设您指的是仅接口,那么是的,这是可能的!诀窍是不在接口或它的类上定义运算符,而是在包装器中-struct - 通过operator implicit 的魔力使用...
虽然有一个小问题,但如果您使用的是 C# 6.0 或更高版本,这完全可以解决...请继续阅读!
您所描述的是 wrapper-structs 在开放通用接口上的(另一个)很好的用例。
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<TImpl,TValue>)。
static operator 方法,它们都接受并返回相同的Operable<TImpl,TValue>。struct Operable 还定义了 implicit operator,用于与 TImpl 和 TValue 的隐式转换,这有助于使这种方法可用。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 );
}
...但它没有!
问题是我们需要将a 或b 隐式提升 为Operable<ComplexNumber,ComplexNumber>,以便调用重载的+ 运算符。
quick-and-dirty 解决方法是在最里面的操作数(根据operator precedence rules)上使用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) 的预期输出。
...然后适用于任何长度和复杂度的表达式,只要记住在 first 操作上使用.Op,而不是最左边(在这种情况下,b.Op和d.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 部分仍然是一个丑陋的疣,但是可以做些什么呢?
嗯,答案分为两部分:
IOperable 的任何类型是否也重载了运算符。
struct Operand,您仍然可以利用重载运算符(尽管使用.Op wart)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 部分...现在对我来说太费力了,因此请考虑将其作为读者练习。
【讨论】:
我会这样做:
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;
【讨论】:
我过去也这样做过……
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)
{
...
}
}
【讨论】:
您可以在抽象基类中实现重载,但将实际操作细节委托给抽象方法。然后这将必须被实现,并且重载将随着它们的实现而被取消。
public abstract class OverLoadingBase
{
public abstract OverLoadingBase DoAdd(OverLoadingBase y);
public static OverLoadingBase operator +(OverLoadingBase x, OverLoadingBase y)
{
return x.DoAdd(y);
}
}
虽然我不确定这是否完整。
【讨论】:
由于它的操作符只能被重载而不能被覆盖,这非常困难。我能想到的最佳解决方案是使用抽象类并像这样重载。
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);
}
}
【讨论】:
有点小题大做,但是...
您可以在基类中提供运算符重载,然后在其中一个类中调用一些已发布的抽象方法来完成工作。
public abstract class MyClass
{
public static MyClass operator +(MyClass c1, MyClass c2)
{
return c1.__DoAddition(c2);
}
protected abstract MyClass __DoAddition(MyClass c2);
}
【讨论】:
MyClass,而不是覆盖类。这使得链接相当冒险,而链接是这里的意图。
public abstract class SomeClass<T> where T : SomeClass<T> 然后让所有方法都将类型 T 作为参数,并为其返回类型。
没有。唯一明智的做法是让单元测试检查使用反射来查找所有具体实现,然后验证此条件。您也可以也通过静态构造函数在运行时做同样的事情,但问题是哪个静态构造函数?
另一种方法是删除运算符并使用基于接口的方法;例如,如果您需要 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)));当然不是很可读!