在C#中创建一个虚拟的通用方法

7

我有一些像这样的基础类:

public class AbstractData
{
    public int ID { get; set; }
}

public class Person: AbstractData
{
    public string Name { get; set; }
}

public class AbstractManager<T> where T: AbstractData
{
    public virtual List<T> GetAll()
    {
    }

    public virtual T GetOne(int id)
    {
    }
}

public class PersonManager: AbstractManager<Person>
{
    public override List<Person> GetAll()
    {
        //...
    }

    public override Person GetOne(int id)
    {
        //...
    }
}

现在,我有一个 Windows Forms 基类,如下所示:
public class BaseForm: Form
{
    public virtual AbstractManager<T> GetManager<T>() where T: AbstractData
    {
        return null;
    }
}

还有一种派生形式:

public class PersonForm: BaseForm
{
    public override AbstractManager<T> GetManager<T>()
    {
        return new PersonManager();
    }
}

问题在于,我在PersonForm类上一直收到编译错误:
无法隐式转换类型“PersonManager”为“AbstractManager”
是否有一种方法,可以创建此虚拟方法并使从BaseForm派生的每个类返回AbstractManager的具体表示形式?
如果我在AbstractManager类上去掉泛型,那么我可以通过编译(进行一些代码更改),但是GetAll方法无法返回List。它必须返回List,这会导致从List转换为List时出现问题。
任何帮助将不胜感激。

2
基本表单 GetManager 方法的通用约束应该是 where T: AbstractData 而不是 where T: AbstractManager,对吗? - Daniel J.G.
1
你能把BaseForm和PersonForm也泛化吗? - Kuba Wyrostek
啊,派生表单?我的理解是表单继承是一个不好的想法。虽然我没有具体的参考资料。 - John Alexiou
2个回答

18
首先,请永远不要这样做:
class C<T>
{
    void M<T>(T t) { }
}

现在我们有两个名为T的东西,它们在作用域内并且它们是不同的。虽然这是合法的,但极其令人困惑。为您的类型参数选择更好的名称。
让我们简化您的示例:
class FruitBasket<T> where T : Fruit { }
class AppleBasket : FruitBasket<Apple> { }
class C
{
    public static FruitBasket<T> GetBasket<T>() where T: Fruit 
    {
        return new AppleBasket();
    }
}

现在你明白这是为什么错误了吗?如果有人调用 C.GetBasket<Orange>(),而你却给他们一篮子苹果,那会怎样?
任何帮助都将不胜感激。
第一步走出困境是什么?停止挖掘。
你患有通用类型幸福病,这是C#程序员常见的病症,他们发现了通用类型系统的强大之处,然后想要将其用于一切,无论是否合理。停止尝试在通用类型系统中捕捉业务流程中的所有关系;这不是它的设计目的。
测试方法是:你能否说“一个苹果篮子是一篮子苹果,其中苹果是一种水果”,并且有一个非程序员同意你的说法?可以。你能否说“人员经理是人员的抽象管理者,其中人员是一种抽象数据”,并且有一个非程序员同意你的说法?不行。那么你就没有成功地在类型系统中对业务领域进行建模。重新开始,避免使用通用类型,并尝试找到有意义的类型之间的关系。

2
LOL,GHD,当你是一把金锤子时,所有的东西都是钉子!我很高兴我们现在有了一种与这种行为相关的疾病。 - Mike Perrenoud
9
“通用幸福病”与“面向对象幸福病”以及“线程幸福病”有一定关联,但流行病学家仍在探讨确切的联系。” - Eric Lippert
4
请写一篇关于“普遍幸福病”的博客文章。我很想在设计讨论中引用这个链接。 - shoelzer

11

通过声明

public virtual AbstractManager<T> GetManager<T>() where T: AbstractData

BaseForm 中,您承诺每个派生自 BaseForm 的类都支持任何类型 TGetManager。例如,如果您有另一个名为 InvoiceAbstractData 子类,则可以编写:
personForm.GetManager<Invoice>()

如果您希望所有从BaseForm继承的类仅支持一种类型TGetManager,那么请将T类型参数从GetManager移动到BaseForm中:

PersonForm应该返回一个InvoiceManager

public class BaseForm<T>: Form where T: AbstractData
{
    public virtual AbstractManager<T> GetManager()
    {
        return null;
    }
}

public class PersonForm: BaseForm<Person>
{
    public override AbstractManager<Person> GetManager()
    {
        return new PersonManager();
    }
}

更新: Chad Henderson 指出,Windows Forms 设计器无法处理泛型基类。如果这对您造成了问题,那么您可以尝试另一种方法:

public interface IForm<T> where T: AbstractData
{
    AbstractManager<T> GetManager();
}

public class BaseForm: Form
{
    // ... base functionality that doesn't depend on T ...
}

public class PersonForm: BaseForm, IForm<Person>
{
    public AbstractManager<Person> GetManager()
    {
        return new PersonManager();
    }
}

这是一个不错且简单的解释,但仅供记录的小注:WinForms 设计器无法处理渲染泛型类(或具有泛型或抽象基类的类)。 - Chad
@Chad:感谢你的留言。这个限制非常不幸。 - Michael Liu

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