C# 中是否有任何方法可以在派生类中强制执行运算符重载?
我需要定义一个接口,它必须对实现它的类型强制执行某些运算符重载。似乎没有明显的方法可以做到这一点,因为运算符重载必须使用类中的静态方法来完成。有什么方法可以达到相同的效果(使用抽象类或其他任何东西)?
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
有点黑客,但是......
您可以在基类中提供运算符重载,然后调用其中一个类中的一些已发布的抽象方法来完成那里的工作。
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.
不。唯一明智的方法是使用反射进行单元测试检查以查找所有具体实现,然后验证此条件。您也可以通过静态构造函数在运行时执行相同的操作,但问题是哪个静态构造函数?
另一种方法是放弃操作符并使用基于接口的方法;例如,如果您需要
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 anAdd(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.您可以在抽象基类中实现重载,但将实际操作细节委托给抽象方法。然后必须实现这一点,并且重载将通过它们的实现来完成。
虽然我不确定这是否完整。
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.
Though I'm not sure if this is complete.
严格来说,类型并不是从
接口
“派生”的,它们只是实现
它 - 但如果您指的是在派生中需要运算符重载>class
来自父类class
那么可以通过使父类class
泛型以允许变体运算符参数和返回类型来完成,但这实际上意味着然后子类不能再次子类化(不使子类通用)。现在,假设您仅引用接口,那么是的,这是可能的!诀窍是不要在接口或类上定义运算符,而是在包装器
struct
中定义 - 它通过隐式运算符的魔力使用< /code>...
虽然有一点问题,但是如果您使用的是 C# 6.0 或更高版本,则完全可以解决...继续阅读!
您所描述的是开放通用接口上的包装器
struct
的(另一个)良好用例。class
) 或值 (struct
),包括任何interface
被包装在一个struct
中,对您的程序来说是不可见的,然后该包装结构会添加您所需的功能。struct
等)在 .NET 中是“自由”的,因为它们不会产生 GC 堆分配。要实现此目的,首先需要定义具有您想要支持的操作的接口。
struct Operable
)。静态运算符
方法,它们都接受并返回相同的Operable
。struct Operaable
还定义了隐式运算符
,用于在TImpl
和TValue 之间进行隐式转换
,这有助于使这种方法可用。TValue
进行隐式转换是不可能的,因为无法知道TImpl
是什么,但您可以在TValue 上定义运算符code>struct Operable<>
允许任一操作数使用原始TValue
,这样它就可以从另一个操作数推断出TImpl
。例如,假设我们有一个自定义数字类型,我们希望在编译时强制执行上述运算符...
只需使其实现
IOperable
,因此此实现定义了 复数:所以这个应该能够像这样使用:
.. .但事实并非如此!
问题是我们需要将
a
或b
隐式提升为Operable
,因此将调用重载的 + 运算符。快速而肮脏的解决方法是在最里面的操作数上使用该
Op
属性(根据运算符优先级规则)来触发到Operable<>
的隐式转换,编译器负责剩下的工作,包括隐式转换回ComplexNumber
:所以这个:
...给出了
(6+4i) + (8--2i) = (14+2i)
的预期输出。...然后可以处理任何长度和复杂性的表达式,只需记住在第一个操作上使用
.Op
,而不是最左边的操作(在这种情况下,两个b.Op
和d.Op
因为它们是独立的操作:当然,
.Op
部分仍然是一个丑陋的疣,但是什么可以解决这个问题吗?答案分为两部分:
struct Operand
,您仍然可以利用重载运算符(尽管带有.Op
疣)partial
类型,包括自动生成隐式运算符
,以适应任何未重载运算符的外部类型。第 1 部分很简单,这是一个简单的 Roslyn 分析器,它将引发警告(或错误,由您自行决定):
只需将上述内容复制并粘贴到 VS 项目模板中的库存 Roslyn 分析器项目中即可。
第 2 部分...现在对我来说太费力了,所以请考虑将其作为读者的练习。
Strictly speaking, types don't "derive" from an
interface
, they onlyimplement
it - but if you are referring to requiring operator overloads in a derivedclass
from a parentclass
then that can be done by making the parentclass
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).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 ofoperator 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-
struct
s over open generic interfaces.class
) or value (struct
), including anyinterface
, is wrapped in astruct
invisibly to your program, and that wrapper-struct then adds the required functionality you're after.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.
struct Operable<TImpl,TValue>
).static operator
methods which all accept and return the the sameOperable<TImpl,TValue>
.struct Operable
also hasimplicit operator
defined for implicit conversion to-and-fromTImpl
and toTValue
, which is what helps make this approach usable.TValue
isn't possible because there's no way of knowing whatTImpl
would be, but you can define operators onstruct Operable<>
that allow rawTValue
for either of the operands, that way it can inferTImpl
from the other operand.So for example, supposing we have a custom number type that we want to enforce at compile-time the operators described above...
Simply make it implement
IOperable
, so this implementation defines most arithmetic operations on complex numbers:So this should be able to be used like so:
...but it doesn't!
The problem is that we need either
a
orb
to be implicitly promoted toOperable<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 toOperable<>
and the compiler takes care of the rest, including implicit conversion back toComplexNumber
:So this:
...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, bothb.Op
andd.Op
due to them being independent operations:Of course, the
.Op
part is still an ugly wart, but what can be done about that?Well, the answer lies in two parts:
IOperable
also has the operators overloaded.struct Operand
you can still take advantage of overloaded operators (albeit with the.Op
wart)partial
type provided elsewhere, including auto-generatingoperator 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):
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.
我过去曾经这样做过......
然后实现如下:
I have done this in the past...
And then implemented as follows:
由于它的运算符只能重载而不能重写,因此非常困难。我能想到的最好的解决方案是使用抽象类并像这样重载。
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.
我会做类似的事情:
然后你可以将 Scalar 继承为:
就是这样:
I'd do something like:
Then you can inherit Scalar as:
And that's it:
由于写了很多答案,似乎发生了一些变化。到 2023 年,您可以使用接口强制运算符重载,而无需使用任何解决方法。
让我们定义接口:
现在让我们实现接口的类:
当我们运行程序时,我们会在控制台上得到以下输出:
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:
Now lets do the class implementing the interface:
When we run the program we get the following output on the console:
从 C# 11 开始有
Since C# 11 there is