“在大多数情况下,基础抽象泛型类是一个糟糕的选择。”为什么? (或者为什么不)

时间:2011-02-18 11:38:13

标签: c# .net generics inheritance

我刚看到对blog帖子的评论:

  

基本抽象泛型类很糟糕   大多数情况下的选择

这是真的,如果不是为什么?

有什么见解导致这种说法?

7 个答案:

答案 0 :(得分:15)

我同意,因为任何继承抽象泛型类的东西都不会与基类一致。也就是说,如果你有

abstract class myBase<T>

然后你创建

class myThing: myBase<thing>
class myOtherThing: myBase<otherThing>

你不能创建适用于myThing和myOtherThing的方法,因为它们不共享祖先。基类中没有任何意义是抽象的,真的,它也可能只是一个类。

但是如果你有一个基类

abstract class myBase
class myBase<T>: myBase

作为泛型类的常见模式(如IEnumerable - 使用接口),然后它们共享myBase。

(编辑)我刚刚阅读了实际的博客文章 - 实际上,评论在那种情况下并不真正有效。他引用的“抽象通用基类”Range<T>继承了继承非通用接口IEnumerable<T>的{​​{1}}。所以它并不是一个“抽象的通用基类”。但一般来说我认为这是真的。

答案 1 :(得分:14)

“大多数情况”是完全模糊的。通用抽象类(或接口)是一个坏主意如果这类的后代之间唯一的共同祖先是System.Object(正如此问题的其他评论者所指出的那样)。

否则(如果你确实有一个有意义的共同祖先),如果你想“重命名”或“专门化”成员,这是个好主意。考虑这个例子:

// Meaningful common ancestor for the working classes.
interface IWorker
{
   object DoWork();
}

// Generic abstract base class for working classes implementations.
abstract WorkerImpl<TResult> : IWorker
{
   public abstract TResult DoWork();

   object IWorker.DoWork()
   {
      return DoWork(); // calls TResult DoWork();
   }
}

// Concrete working class, specialized to deal with decimals.
class ComputationWorker : WorkerImpl<decimal>
{
    override decimal DoWork()
    {
       decimal res;

       // Do lengthy stuff...

       return res;
    }
}

在这个例子中,DoWork()在抽象类中被重新定义,变得具体且专注于ComputationWorker

答案 2 :(得分:4)

抽象通用基类的一个问题是你不能输入decorate:

public abstract class Activity<TEntity>
{
  public Activity() { }

  protected virtual object Implementation { ... }
}

public abstract class CompensableActivity<TEntity,TCompensation> : Activity<TEntity>
  where TCompensation : Activity<T>, new()
{
  public CompensableActivity() { }

  protected override object Implementation
  {
    get { new Wrapper(base.Implementation, Compensation); }
  }

  private Activity<TEntity> Compensation
  {
    get
    {
      var compensation = new TCompensation();
      if(compensation is CompensableActivity<TEntity,Activity<TEntity>)
      {
        // Activity<TEntity> "does not meet new() constraint" !
        var compensable = comp as CompensableActivity<TEntity, Activity<TEntity>>;
        var implement = compensable.Implementation as Wrapper;
        return implement.NormalActivity;
      }
      else { return compensation; }
    }
  }
}

答案 3 :(得分:3)

Kragen提出了一个很好的观点。任何低于你代码50%的东西都是“大多数情况下的错误选择”。

然而,即使你的类中有近50%的类应该是通用的抽象基类,但它也不比其他任何语言特性好。

例如,BindingList<T>可以被视为抽象通用基类。它是一个通用容器,排序要求您从中派生并覆盖ApplySortCore

KeyedCollection<TKey, TItem>不仅仅是一个抽象的通用基类,它只是一个。要使用它,你必须从中导出并实现GetKeyForItem

答案 4 :(得分:3)

我不同意这里的人群。在某些情况下,抽象泛型类是最好的设计。 .NET中的一个很好的例子可以在System.Collections.ObjectModel中找到,其中KeyedCollection允许您轻松覆盖和实现类型化的可序列化字典集合!

我省略了大部分代码,但这是原则:

public class NameCollection : System.Collections.ObjectModel.KeyedCollection<string, INamedObj>
{

    protected override string GetKeyForItem(INamedObj item)
    {
        return item.Name;
    }
}

答案 5 :(得分:2)

嗯,从统计学上讲,如果有人问我“我应该把这个班级变成一个抽象的通用类吗?”,答案几乎肯定是没有 - 在3年的.Net开发中我认为我可以一方面计算我编写的具有泛型类型参数的抽象类的数量。

除此之外,我看不出抽象泛类被认为是坏事的任何特殊原因 - 它只是不常见。

答案 6 :(得分:-2)

在某些情况下,抽象基类是有用的。

我们有几个使用不同数据库引擎的.net应用程序。几个sql server,几个mysql和一堆oracle应用程序。

我们有一个通用的公共数据库对象,它基于一个工厂,它根据数据库的类型在工厂设置中返回正确的数据库对象。

这样,如果我正在启动一个新的应用程序,我所要做的就是加载这个数据库对象,传入类型,传入连接字符串,然后bam ...我已经设置...

我想我想说的是这完全取决于背景及其实际使用方式。