代码之家  ›  专栏  ›  技术社区  ›  Daniel Plaisted

在.NET中使用隐式转换替代多重继承

  •  3
  • Daniel Plaisted  · 技术社区  · 15 年前

    我有一种情况,我希望有一个特定类型的对象能够被用作两种不同的类型。如果其中一个“基本”类型是一个接口,这就不是问题,但在我的例子中,它们最好都是具体的类型。

    我正在考虑将某个基类型的方法和属性的副本添加到派生类型,并将派生类型到该基类型的隐式转换添加到派生类型。然后,用户可以通过直接使用重复的方法、将派生类型分配给基类型的变量或将其传递给接受基类型的方法,将派生类型视为基类型。

    看起来这个解决方案很适合我的需要,但是我遗漏了什么吗?是否存在这样一种情况:在使用API时,这可能会增加混乱而不是简单性?

    编辑: 有关我的特定场景的更多详细信息:

    RightEdge ,这是一个自动化交易系统开发环境。价格数据表示为一系列条形图,这些条形图具有给定时期(1分钟、1天等)的开盘价、低价、高价和收盘价的值。指标对一系列数据进行计算。一个简单指标的例子是移动平均指标,它给出了最近 其输入值,其中 n个

    每次新的条进入时,指示器都会计算该条输出的新值。

    大多数指示器只有一个输出序列,但有时可以方便地有多个输出(请参见 MACD

    因此,指标需要从一个“组件”类派生,该类具有新数据传入时调用的方法。然而,对于只有一个输出序列(这是大多数输出序列)的指标来说,它们本身作为一个序列是有好处的。这样,用户可以使用 SMA.Current 对于形状记忆合金的当前值,而不是必须使用 SMA.Output.Current . 同样地, Indicator2.Input = Indicator1; Indicator2.Input = Indicator1.Output; . 这看起来没什么区别,但我们的很多目标客户都不是专业的.NET开发人员,所以我希望尽可能简单。

    我的想法是,对于只有一个输出序列的指标,将其从指标隐式转换为输出序列。

    6 回复  |  直到 15 年前
        1
  •  4
  •   Edurne Pascual Stefan Thyberg    5 年前

    你没有提供太多的细节,所以这里试图从你所提供的回答。

    看看基本的区别:
    B 以及派生类型 D ,像这样的作业:

    B my_B_object = my_D_object;
    

    是具有隐式转换的独立类型,上面的赋值将 创建副本 属于 my_D_object 是一个类)在 my_B_object

    总之,对于“real”,继承是通过引用工作的(对引用的更改会影响许多引用共享的对象),而自定义类型转换通常是通过值工作的(这取决于您如何实现它,但是,为转换器实现类似于“按引用”行为的东西几乎是疯狂的:每个引用都指向自己的对象。

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

    对每个“base”类型执行此操作将允许您为想要的方法提供默认实现。

    这些方法不会表现为开箱即用的虚拟方法;但是您可以使用反射来实现;您需要在Helper类的实现中执行以下操作:

    1. 检索 System.Type 具有 value.GetType()
    2. 如果找到匹配的方法,则调用该方法并返回(这样助手的其余方法就不会运行)。

    这就是:C#中的多重继承,唯一需要注意的是在支持它的基类中需要一些难看的代码,以及由于反射而产生的一些开销;但是除非您的应用程序在巨大的压力下工作,否则这应该可以做到。

    所以,再说一遍,为什么不想使用接口?如果唯一的原因是他们无法提供方法实现,那么上面的技巧就解决了这个问题。如果您对接口有任何其他问题,我可能会尝试解决它们,但我必须先了解它们;)

    希望这有帮助。


    [编辑:根据评论添加]

    我在原来的问题上增加了一些细节。我不想使用接口,因为我想防止用户错误地实现接口,或者意外地调用一个方法(如NewBar),从而让用户自取灭亡,如果他们想实现一个指示符,则需要重写该方法,但他们不应该直接调用该方法。

    我看过你的最新问题,但评论很概括。也许我遗漏了一些东西,但是接口+扩展+反射可以解决多重继承所能解决的所有问题,并且比任务中的隐式转换要好得多:

    • 虚拟方法行为(提供了一个实现,继承者 可以 override):帮助器上的include方法(包装在上面描述的反射“虚拟化”中),不在接口上声明。
    • 抽象方法行为(未提供实现,继承者 implement):在接口上声明方法,不要在帮助程序上包含它。
    • 可能隐藏 但是 ):只需在帮助程序上正常实现它。
    • 额外的好处:奇怪的方法(提供了一个实现,但是继承者 必须 在接口上声明它。我不知道它是如何工作的(在虚拟和非虚拟方面),也不知道它会有什么用途,但是嘿,我的解决方案已经击败了多重继承:P

    注意:对于非虚拟方法,您需要将接口类型设置为“声明”类型,以确保使用基本实现。这与继承者隐藏方法时完全相同。

    似乎非虚拟(仅在helper上实现)在这里工作得最好。

    这就是抽象方法(或接口,这是一种超抽象的东西)最引人注目的地方。继承人 必须 实现该方法,否则代码甚至无法编译。在某些情况下,虚拟方法可能会这样做(如果您有一个通用的基本实现,但更具体的实现是合理的)。

    但他们不应该直接打电话

    如果一个方法(或任何其他成员)暴露在客户机代码中,但不应该从客户机代码中调用,则没有任何编程解决方案来强制执行该方法(实际上,有,忍受我)。文档上的正确地址。因为 你是

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

    当然,如果你有 using System.Reflection;

        2
  •  1
  •   dtb    15 年前

    我看到两个问题:

    • 对于隐式用户定义的类型转换运算符,应用该运算符时通常不明显。

    这并不是说你根本就不应该定义类型转换运算符,但是在设计解决方案时必须记住这一点。

    一个容易发现、容易识别的解决方案是定义显式转换方法:

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

    void PayMusician(Musician musician) { GiveMoney(musician, musician.Wage); }
    
    void PayStudent(Student student) { GiveMoney(student, student.Wage); }
    
    StudentMusician alice;
    PayStudent(alice.AsStudent());
    
        3
  •  1
  •   Ben Voigt    15 年前

    听起来好像你的方法不支持交叉转换。真正的多重继承会。

    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
    
        4
  •  0
  •   Reed Copsey    15 年前

    然后,用户可以通过直接使用重复的方法、将派生类型分配给基类型的变量或将其传递给接受基类型的方法,将派生类型视为基类型。

    新的 转换发生时的对象。这可能是非常出乎意料的,因为在这两种情况下,它的表现会大不相同。

    就我个人而言,我会把它作为一个返回新类型的方法,因为它会让最终用户看到实际的实现。

        5
  •  0
  •   dtb    15 年前

    也许我对此做得太过分了,但是您的用例听起来很可疑,好像它可以从构建 Rx Rx in 15 Minutes ).

    Rx是一个用于处理产生值的对象的框架。它允许以一种非常有表现力的方式组合这些对象,并转换、过滤和聚合这些产生的值流。

    你说你有一个酒吧:

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

    序列是生成条的对象:

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

    移动平均值指示器是产生上一个平均值的对象 每当生产新棒料时:

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

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

    具有多个输出的指示器类似于GroupBy。

        6
  •  0
  •   Jörg W Mittag    15 年前

    这可能是一个愚蠢的想法,但是:如果您的设计需要多重继承,那么为什么不简单地使用一种带有MI的语言呢?有几种.NET语言支持多重继承。我的头顶:埃菲尔,巨蟒,看起来。可能还有更多。