在 .NET 中使用隐式转换替代多重继承
我有一种情况,我希望某种类型的对象能够用作两种不同类型。如果“基本”类型之一是接口,这不会成为问题,但就我而言,最好它们都是具体类型。
我正在考虑将基类型之一的方法和属性的副本添加到派生类型,并添加从派生类型到该基类型的隐式转换。然后,用户将能够通过直接使用重复的方法、将派生类型分配给基类型的变量或将其传递给采用基类型的方法,将派生类型视为基类型。
看起来这个解决方案很适合我的需求,但是我错过了什么吗?是否存在这种情况不起作用,或者在使用 API 时可能会增加混乱而不是简单性?
编辑:有关我的具体场景的更多详细信息:
这是为了将来可能重新设计 RightEdge,这是一个自动交易系统开发环境。价格数据表示为一系列条形图,其中包含给定时间段(1 分钟、1 天等)的开盘价、最低价、最高价和收盘价的值。指标对一系列数据进行计算。简单指标的一个示例是移动平均指标,它给出其输入的最新 n 值的移动平均值,其中 n 是用户指定的。移动平均线可以应用于收盘价柱,也可以应用于另一个指标的输出以使其平滑。
每次出现新的柱时,指标都会计算该柱的输出的新值。
大多数指标只有一个输出系列,但有时有多个输出会很方便(请参阅MACD),我想支持这一点。
因此,指标需要从“Component”类派生,该类具有新数据进入时调用的方法。但是,对于只有一个输出系列(这是其中的大多数)的指标,这对它们来说是有好处的自己充当一个系列。这样,用户就可以使用 SMA.Current
作为 SMA 的当前值,而不必使用 SMA.Output.Current
。同样,Indicator2.Input = Indicator1;
优于 Indicator2.Input = Indicator1.Output;
。这看起来似乎没有太大区别,但我们的许多目标客户都不是专业的 .NET 开发人员,所以我想让这尽可能简单。
我的想法是对于只有一个输出系列的指标,从指标到其输出系列进行隐式转换。
I have a situation where I would like to have objects of a certain type be able to be used as two different types. If one of the "base" types was an interface this wouldn't be an issue, but in my case it is preferable that they both be concrete types.
I am considering adding copies of the methods and properties of one of the base types to the derived type, and adding an implicit conversion from the derived type to that base type. Then users will be able treat the derived type as the base type by using the duplicated methods directly, by assigning it to a variable of the base type, or by passing it to a method that takes the base type.
It seems like this solution will fit my needs well, but am I missing anything? Is there a situation where this won't work, or where it is likely to add confusion instead of simplicity when using the API?
EDIT: More details about my specific scenario:
This is for a potential future redesign of the way indicators are written in RightEdge, which is an automated trading system development environment. Price data is represented as a series of bars, which have values for the open, low, high, and close prices for a given period (1 minute, 1 day, etc). Indicators perform calculations on series of data. An example of a simple indicator is the moving average indicator, which gives the moving average of the most recent n values of its input, where n is user-specified. The moving average might be applied to the bar close, or it could be applied to the output of another indicator to smooth it out.
Each time a new bar comes in, the indicators compute the new value for their output for that bar.
Most indicators have only one output series, but sometimes it is convenient to have more than one output (see MACD), and I want to support this.
So, indicators need to derive from a "Component" class which has the methods that are called when new data comes in. However, for indicators which have only one output series (and this is most of them), it would be good for them to act as a series themselves. That way, users can use SMA.Current
for the current value of an SMA, instead of having to use SMA.Output.Current
. Likewise, Indicator2.Input = Indicator1;
is preferable to Indicator2.Input = Indicator1.Output;
. This may not seem like much of a difference, but a lot of our target customers are not professional .NET developers so I want to make this as easy as possible.
My idea is to have an implicit conversion from the indicator to its output series for indicators that have only one output series.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您没有提供太多详细信息,因此这里尝试根据您提供的内容进行回答。
看一下基本差异:
当您有基类型
B
和派生类型D
时,如下所示的赋值:将引用分配给同一对象。另一方面,当
B
和D
是独立类型并且它们之间存在隐式转换时,上述赋值将创建的副本
>my_D_object
并将其存储在my_B_object
上(如果B
是一个类,则存储对其的引用)。总之,“真正的”继承通过引用工作(对引用的更改会影响许多引用共享的对象),而自定义类型转换通常按值工作(这取决于您如何实现它,但实现接近“通过引用”的东西) “转换器的行为几乎是疯狂的):每个引用都将指向它自己的对象。
你说你不想使用接口,但为什么呢?使用组合接口 + 辅助类 + 扩展方法(需要 C# 3.0 和 .Net 3.5 或更高版本)可以非常接近真正的多重继承。看看这个:
对每个“基本”类型执行此操作将允许您为所需的方法提供默认实现。
这些方法不会像开箱即用的虚拟方法一样运行;但你可以使用反射来实现这一点;您需要在 Helper 类的实现中执行以下操作:
value.GetType()
检索System.Type
就是这样:C# 中的多重继承,唯一需要注意的是在支持此功能的基类中需要一些丑陋的代码,以及由于反射而产生的一些开销;但除非您的应用程序在很大的压力下工作,否则这应该可以解决问题。
那么,再一次,为什么你不想使用接口呢?如果唯一的原因是他们无法提供方法实现,上面的技巧就可以解决它。如果您对接口有任何其他问题,我可能会尝试解决它们,但我必须首先了解它们;)
希望这会有所帮助。
[编辑:根据评论添加]
我已经查看了您更新的问题,但评论非常概括了它。也许我错过了一些东西,但是接口+扩展+反射可以解决多重继承可以解决的所有问题,并且在任务中比隐式转换要好得多:
注意:在这种情况下对于非虚拟方法,您需要将接口类型作为“声明”类型以确保使用基本实现。这与继承者隐藏方法完全相同。
似乎非虚拟(仅在助手上实现)在这里效果最好。
这就是抽象方法(或接口,这是一种超抽象的东西)最闪耀的地方。继承者必须实现该方法,否则代码甚至无法编译。在某些情况下,虚拟方法可能会这样做(如果您有一个通用的基本实现,但更具体的实现是合理的)。
如果一个方法(或任何其他成员)暴露给客户端代码但不应该从客户端代码调用,则没有编程解决方案来强制执行该操作(实际上,有,请耐心等待)。解决这个问题的正确位置是在文档中。因为您正在记录您的API,不是吗? ;) 转换和多重继承都无法帮助您。但是,反射可能会有所帮助:
当然,如果文件中有
using System.Reflection;
语句,则可以缩短该时间。而且,顺便说一句,请随意将异常的类型和消息更改为更具描述性的内容;)。You don't provide too many details, so here is an attempt to answering from what you provide.
Take a look at the basic differences:
When you have a base type
B
and a derived typeD
, an assignment like this:assigns a reference to the same object. On the other hand, when
B
andD
are independent types with an implicit conversion between them, the above assignment would create a copy ofmy_D_object
and store it (or a reference to it ifB
is a class) onmy_B_object
.In summary, with "real" inheritance works by reference (changes to a reference affect the object shared by many references), while custom type conversions generally work by value (that depends on how you implement it, but implementing something close to "by reference" behavior for converters would be nearly insane): each reference will point to its own object.
You say you don't want to use interfaces, but why? Using the combo interface + helper class + extension methods (C# 3.0 and .Net 3.5 or newer required) can get quite close to real multiple inheritance. Look at this:
Doing that for each "base" type would allow you to provide default implementations for the methods you want to.
These won't behave as virtual methods out-of-the-box; but you may use reflection to achieve that; you would need to do the following from within the implementation on the Helper class:
System.Type
withvalue.GetType()
There you go: multiple inheritance in C#, with the only caveat of requiring some ugly code in the base classes that will support this, and some overhead due to reflection; but unless your application is working under heavy pressure this should do the trick.
So, once again, why you don't want to use interfaces? If the only reason is their inability to provide method implementations, the trick above solves it. If you have any other issue with interfaces, I might try to sort them out, but I'd have to know about them first ;)
Hope this helps.
[EDIT: Addition based on the comments]
I've looked at your updated question, but the comment quite summarizes it. Maybe I'm missing something, but interfaces + extensions + reflection can solve everything multiple inheritance could, and fares far better than implicit conversions at the task:
Note: On the case of the non-virtual method, you'd need to have the interface type as the "declared" type to ensure that the base implementation is used. That's exactly the same as when an inheritor hides a method.
Seems that non-virtual (implemented only on the helper) will work best here.
That's where abstract methods (or interfaces, which are a kind of super-abstract thing) shine most. The inheritor must implement the method, or the code won't even compile. On some cases virtual methods may do (if you have a generic base implementation but more specific implementations are reasonable).
If a method (or any other member) is exposed to client code but shouldn't be called from client code, there is no programmatic solution to enforce that (actually, there is, bear with me). The right place to address that is on the documentation. Because you are documenting you API, aren't you? ;) Neither conversions nor multiple inheritance could help you here. However, reflection may help:
Of course, you may shorten that if you have a
using System.Reflection;
statement on your file. And, BTW, feel free to change the Exception's type and message to something more descriptive ;).我发现两个问题:
用户定义的类型转换运算符通常不太容易被发现——它们不会出现在 IntelliSense 中。
对于隐式用户定义类型转换运算符,应用该运算符时通常不明显。
对于
这并不是说您根本不应该定义类型转换运算符,但在设计解决方案时必须牢记这一点。
一个易于发现、易于识别的解决方案是定义显式转换方法:
用法:
I see two issues:
User-defined type conversion operators are generally not very discoverable -- they don't show up in IntelliSense.
With an implicit user-defined type conversion operator, it's often not obvious when the operator is applied.
This doesn't been you shouldn't be defining type conversion operators at all, but you have to keep this in mind when designing your solution.
An easily discoverable, easily recognizable solution would be to define explicit conversion methods:
Usage:
听起来您的方法似乎不支持交叉转换。真正的多重继承会。
来自 C++ 的示例,具有多重继承:
It doesn't sound as if your method would support a cross-cast. True multiple inheritance would.
An example from C++, which has multiple inheritance:
然而,这会表现不同。在继承的情况下,您只需传递对象。但是,通过实现隐式转换器,在转换发生时您将始终构造一个新对象。这可能是非常出乎意料的,因为在这两种情况下它的表现会完全不同。
就我个人而言,我会将其设为返回新类型的方法,因为它将使最终用户清楚实际的实现。
This will behave differently, however. In the case of inheritance, you're just passing your object. However, by implementing an implicit converter, you'll always be constructing a new object when the conversion takes place. This could be very unexpected, since it will behave quite differently in the two cases.
Personally, I'd make this a method that returns the new type, since it would make the actual implementation obvious to the end user.
也许我对此太过分了,但您的用例听起来很可疑,好像它可以从 Rx (15 分钟内接收)。
Rx 是一个用于处理生成值的对象的框架。它允许这些对象以一种非常富有表现力的方式组合,并转换、过滤和聚合这些产生的值流。
您说您有一个柱:
系列是生成柱的对象:
移动平均线指标是每当生成新柱时生成最后 count 柱的平均值的对象:
用法是如下:
具有多个输出的指标类似于 GroupBy。
Maybe I'm going too far off with this, but your use case sounds suspiciously as if it could heavily benefit from building on Rx (Rx in 15 Minutes).
Rx is a framework for working with objects that produce values. It allows such objects to be composed in a very expressive way and to transform, filter and aggregate such streams of produced values.
You say you have a bar:
A series is an object that produces bars:
A moving average indicator is an object that produces the average of the last count bars whenever a new bar is produced:
The usage would be as follows:
An indicator with multiple outputs is similar to GroupBy.
这可能是一个愚蠢的想法,但是:如果您的设计需要多重继承,那么为什么不简单地使用具有 MI 的语言呢?有多种 .NET 语言支持多重继承。我的脑海中浮现出:Eiffel、Python、Ioke。可能还有更多。
This might be a stupid idea, but: if your design requires multiple inheritance, then why don't you simply use a language with MI? There are several .NET languages which support multiple inheritance. Off the top of my head: Eiffel, Python, Ioke. There's probable more.