泛型,继承和新的运算符

时间:2009-03-19 19:07:18

标签: c# generics inheritance oop

以下是我不时使用的内容,我只是希望得到一些有关实践优点的反馈。

让我们说我有一个基类:

abstract class RealBase {
    protected RealBase(object arg) {
        Arg = arg;
    }

    public object Arg { get; private set; }

    public abstract void DoThatThingYouDo();
}

我经常创建一个通用的第二个基类来处理从基类中的“object”类型到“T”类型的转换,如下所示:

abstract class GenericBase<T> : RealBase {
    protected GenericBase(T arg)
        : base( arg ) {
    }

    new public T Arg { get { return (T) base.Arg; } }
}

这允许我在没有强制转换操作的情况下访问“Arg”作为其显式类型:

class Concrete : GenericBase<string> {
    public Concrete( string arg )
        : base( arg ) {
    }

    public override void DoThatThingYouDo() {
        // NOTE: Arg is type string. No cast necessary.
        char[] chars = Arg.ToLowerInvariant().ToCharArray();  
        // Blah( blah, blah );
        // [...]
    }

}

一直能够通过“RealBase”使用它:

class Usage {
    public void UseIt() {
        RealBase rb = new Concrete( "The String Arg" );
        DoTheThing(rb);
    }

    private void DoTheThing(RealBase thingDoer) {
        rb.DoThatThingYouDo();
    }
}

假设还有许多其他“混凝土”类型......而不仅仅是那个。

以下是我的问题/疑虑:

  1. 我是否“脱离摇杆”使用 像这样的方法?
  2. 有吗? 任何明显的缺点/警告 用这种方法?
  3. 怎么样? 那个“新公共T ......” GenericBase?好/坏主意?别扭?
  4. 非常感谢任何反馈或建议。

6 个答案:

答案 0 :(得分:5)

我没有任何明确的反对意见,只要你足够自律,只使用通用基类作为助手,而不是向下传播它。如果你开始在所有地方开始引用RealBase和GenericBase以及ConcreteClass,那么事情就会很快变得紧密耦合。

事实上,我建议将其提升一个档位并引入界面

interface IReal {
  void DoThatThingYouDo();
}

完全离开基类(基本上从不引用它,除非在声明派生类时)。只是一个提示,可以帮助我提高代码的灵活性。

哦,如果你确实使用了一个接口,不要只在基类中声明它,在具体的上面声明它:

class MyConcrete: BaseClass<MyConcrete>, IReal {
  ...
}

作为提醒,基类并不重要,只有它的重要性!

答案 1 :(得分:5)

好吧,我们现在已经有了三层深度的继承树,但是你没有给出任何特别的理由。

我个人很少使用继承(或者更确切地说,很少设计我自己的继承层次结构,而不是实现接口并直接从对象派生)。继承是一种强大的工具,但难以有效使用。

如果这给你一些明显优于其他方法的优势,那很好 - 但我会考虑你为阅读代码的人添加的复杂性。他们真的想要考虑三个层次结构,两个同名属性? (我会将GenericBase.Arg重命名为GenericBase.GenericArg或类似的东西。)

答案 2 :(得分:2)

我认为通过使用接口而不是双抽象基类,您将能够获得相同的功能,请考虑这一点:

public interface IAmReal
{
    void DoThatThingYouDo();
    ...
}

abstract class GenericBase<T> : IAmReal
{
    protected GenericBase<T>(T arg)
    {
        Arg = arg;
    }
    public T Arg { get; set; }
    public abstract void DoThatThingYouDo();
}

class MyConcrete : GenericBase<string>
{
    public MyConcrete(string s) : base(s) {}
    public override void DoThatThingYouDo()
    {
        char[] chars = Arg.ToLowerInvariant().ToCharArray();
        ...
    }
}

class Usage
{
    public void UseIt()
    {
        IAmReal rb = new MyConcrete( "The String Arg" );
        DoTheThing(rb);
    }

    private void DoTheThing(IAmReal thingDoer)
    {
        rb.DoThatThingYouDo();
    }
}

答案 3 :(得分:1)

我不认为你是摇摆不定的。有关更复杂的内容,请参阅Curiously Recurring Template

答案 4 :(得分:0)

就我个人而言,我强烈建议不要使用新操作符,因为这可能会导致混淆。实际上我自己最近遇到过这样的问题,但我选择了以AsObject为后缀的基类摘要,即:

public BaseClass{
    public abstract object ValueAsObject {get;set;}
}

和沿线的泛型类:

public BaseClass<T> : BaseClass {
    public T Value {get;set;}
    public override object ValueAsObject {
       get{return (T)this.Value;} 
       set{this.Value = value;} // or conversion, e.g. string -> int
}

Georg Mauer关于DoThatThingYouDo接口的提议也很好。

答案 5 :(得分:0)

我是唯一一个认为应该尽可能避免继承的人吗?我已经看到了继承引起奇怪问题的不同实现,而不仅仅是在尝试向下转换时。接口是救星!我并不是说应该始终避免继承,但只要查看指定的代码,就无法看到这个继承树优于常规接口的优点。