开发者

Using implicit conversion as a substitute for multiple inheritance in .NET

开发者 https://www.devze.com 2022-12-27 02:29 出处:网络
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 ca

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 situat开发者_开发知识库ion 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.


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 type D, an assignment like this:

B my_B_object = my_D_object;

assigns a reference to the same object. On the other hand, when B and D are independent types with an implicit conversion between them, the above assignment would create a copy of my_D_object and store it (or a reference to it if B is a class) on my_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:

interface MyType { ... }
static class MyTypeHelper {
    public static void MyMethod(this MyType value) {...}
}

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:

  1. retrieve a System.Type with value.GetType()
  2. find if that type has a method matching the signature
  3. if you find a matching method, invoke it and return (so the rest of the Helper's method is not run).
  4. Finally, if you found no specific implementation, let the rest of the method run and work as a "base class implementation".

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 added a bunch of details to the original question. I don't want to use interfaces because I want to prevent users from shooting themselves in the foot by implementing them incorrectly, or accidentally calling a method (ie NewBar) which they need to override if they want to implement an indicator, but which they should never need to call directly.

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:

  • Virtual method behavior (an implementation is provided, inheritors can override): include method on the helper (wrapped in the reflection "virtualization" described above), don't declare on the interface.
  • Abstract method behavior (no implementation provided, inheritors must implement): declare method on the interface, don't include it on the helper.
  • Non-virtual method behavior (an implementation is provided, inheritors may hide but can't override): Just implement it as normal on the helper.
  • Bonus: weird method (an implementation is provided, but inheritors must implement anyway; they may explicitly invoke the base implementation): that's not doable with normal or multiple inheritance, but I'm including it for completeness: that's what you'd get if you provide an implementation on the helper and also declare it on the interface. I'm not sure of how would that work (on the aspect of virtual vs. non-virtual) or what use it'd have, but hey, my solution has already beaten multiple inheritance :P

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.

I want to prevent users from shooting themselves in the foot by implementing them incorrectly

Seems that non-virtual (implemented only on the helper) will work best here.

or accidentally calling a method (ie NewBar) which they need to override if they want to implement an indicator

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

but which they should never need to call directly

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:

if(System.Reflection.Assembly.GetCallingAssembly()!=System.Reflection.Assembly.GetExecutingAssembly())
    throw new Exception("Don't call me. Don't call me!. DON'T CALL ME!!!");

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


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:

class Person { }

abstract class Student : Person
{
    public abstract decimal Wage { get; }
}

abstract class Musician : Person
{
    public abstract decimal Wage { get; }
}

class StudentMusician : Person
{
    public decimal MusicianWage { get { return 10; } }

    public decimal StudentWage { get { return 8; } }

    public Musician AsMusician() { return new MusicianFacade(this); }

    public Student AsStudent() { return new StudentFacade(this); }
}

Usage:

void PayMusician(Musician musician) { GiveMoney(musician, musician.Wage); }

void PayStudent(Student student) { GiveMoney(student, student.Wage); }

StudentMusician alice;
PayStudent(alice.AsStudent());


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:

class A {};
class B {};
class C : public A, public B {};

C o;
B* pB = &o;
A* pA = dynamic_cast<A*>(pB); // with true MI, this succeeds


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.

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.


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:

class Bar
{
    double Open { get; }
    double Low { get; }
    double High { get; }
    double Close { get; }
}

A series is an object that produces bars:

class Series : IObservable<Bar>
{
    // ...
}

A moving average indicator is an object that produces the average of the last count bars whenever a new bar is produced:

static class IndicatorExtensions
{
    public static IObservable<double> MovingAverage(
        this IObservable<Bar> source,
        int count)
    {
        // ...
    }
}

The usage would be as follows:

Series series = GetSeries();

series.MovingAverage(20).Subscribe(average =>
{
    txtCurrentAverage.Text = average.ToString();
});

An indicator with multiple outputs is similar to GroupBy.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消