从非泛型类中重写抽象的通用方法

9

基础类

class Drawer
{
    public abstract void Draw<T>(T type);    
}

派生类#1

class ADrawer : Drawer
{
    public override void Draw<T>(List<T> list)
    {
        foreach (var a in list)
        {
            DrawA(a);
        }
    }

    public void DrawA(Agent a)
    {
        //draw code here
    }
}

derived class #2

class AnotherDrawer : Drawer
{
    public override void Draw<T>(T number)
    {
        if (number == 1)
        {
            //draw code
        }
    }
}

错误在于#1派生类中:“没有合适的方法可以重载”。

我在基类中是否应该同时使用“virtual”和“abstract”?

我该如何设置基类参数类型以允许派生类中有各种不同类型的参数?


只是一点小提示,抽象方法隐式地是虚方法,因此您永远不必将方法定义为抽象和虚。 - docmanhattan
2
你有两种概念上不同的方法。其中一个 "Draw" 方法 绘制一件东西,而另一个则 绘制多个东西。你不应该试图将它们合并成一个方法;而是要创建两个方法:Draw<T>(T item)DrawMany<T>(IEnumerable<T> items)。就像 List<T>AddAddRange 方法一样;对单个物品进行操作和对多个物品进行操作是两个不同的操作,因此需要使用两个不同的方法。 - Eric Lippert
3个回答

12

您的代码存在比您询问的问题更多的问题。暂时搁置覆盖问题,类ADrawer需要一个类型约束(where T : Agent):

class ADrawer : Drawer 
{ 
    public void Draw<T>(List<T> list) where T : Agent
    { 
        foreach (var a in list) 
        { 
            DrawA(a); 
        } 
    }
    public void DrawA(Agent a) 
    { 
        //draw code here 
    } 
} 

没有这个约束条件,将a传递给DrawA是不合法的,因为a是类型T的引用,在没有这个约束条件的情况下,从类型T到类型Agent没有隐式转换。

AnotherDrawer类对==运算符有一个非法使用。无法将类型为Tint的操作数应用于==运算符。您可以通过使用object.Equals重载来解决这个问题。

最后,基类中存在一个错误,因为它是一个包含抽象成员的非抽象类。

总的来说,这段代码表明应该使成为泛型,而不是方法

abstract class Drawer<T>
{
    public abstract void Draw(T type);
}

派生类 #1

class ADrawer : Drawer<List<Agent>>
{
    public override void Draw(List<Agent> list)
    {
        foreach (var a in list)
        {
            DrawA(a);
        }
    }       

    public void DrawA(Agent a)
    {
        //draw code here
    }
}

派生类#2

class AnotherDrawer : Drawer<int>
{
    public override void Draw(int number)
    {
        if (number == 1)
        {
            //draw code
        }
    }
}

回应Eric Lippert的评论,这也是我对你的问题的第一反应,你可以考虑采用以下设计:

abstract class Drawer<T>
{
    public abstract void Draw(T type);
    public void DrawMany(IEnumerable<T> types)
    {
        foreach (var t in types)
            Draw(t);
    }
}

派生类 #1

class ADrawer : Drawer<Agent>
{
    public override void DrawA(Agent a)
    {
        //draw code here
    }
}

第二个派生类没有变化。


@Whiplash450,你想要一个抽屉列表,其中抽屉绘制不同类型的对象吗?如果是这样,请查看这个问题以及我的答案:https://dev59.com/hF_Va4cB1Zd3GeqPYvrM。有几种可能的解决方案,但没有一种像你希望的那样流畅。 - phoog
@Whiplash450 最佳解决方案会有一些依赖于你获取要绘制的对象的上下文。它们是否也在你正在迭代的集合中(在这种情况下,对对象的引用将是一个共同的基础类型)?还是你有一个更具体的静态引用指向一个对象,并且你需要为该引用获取正确的绘图器? - phoog
分开的抽屉的目的是为了让每个抽屉都有一个单独的责任,负责绘制一个元素类型(例如代理、节点等)。这些抽屉在运行时接收一个对象列表。但也有其他不需要对象列表而只接收整数参数的抽屉。我希望将所有的抽屉都保存在一个列表中,以便可以在一个循环中更新它们。 - Whiplash450
由于它们在其绘制方法中采用不同的参数,因此必须分别进行绘制调用(我认为这是不幸但不可避免的)。由于基础Drawer类现在是“类型化”的,这是否意味着存储所有抽屉的唯一方法是使用自定义列表类? - Whiplash450
这是一种可能性,但它只是把问题转移了。像你建议的那样改变绘制方法,意味着为了保持私有数据的实时性,更新方法现在必须将私有类型作为参数传递。这解决了一个问题,但又创建了另一个问题。一个混乱的解决方案是为每个抽屉创建一个独特的更新方法。 - Whiplash450
显示剩余4条评论

5

抽象方法应该具有这个签名

  public abstract void Draw<T>(List<T> type);  

啊,好的,但是我有多个从基础类型派生出来的子类,每个子类都使用不同的参数类型,那么如何“通用地”设置基础参数呢? - Whiplash450
1
我建议让基类具有通用参数,然后派生类可以实现 Drawer<int>Drawer<Agent> 等。 - George Duckett

1

要使其编译,请将基类更改为以下内容:

class Drawer
{
    public abstract void Draw<T>(List<T> type);    
}

List<T> 不同于 T,所以当你在派生类的方法中传递一个 List<T> 时,你不能覆盖基类方法,因为它有一个 T 参数而不是一个 List<T> 参数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接