在 C# 中存储泛型类型引用

本文关键字:泛型类型 引用 存储 | 更新日期: 2023-09-27 18:00:05

我正在将我的一些 Java 代码移植到 C#,但在复制此行为时遇到问题:

JAVA CODE *****

public abstract class Fruit<T extends Fruit>{
   //Fruit implementation
}

这很棒,因为我只想要扩展 Fruit 的泛型类型。然后我可以像这样存储所有混凝土水果对象的引用:

Banana banana = new Banana(); //This class extends Fruit
Strawberry strawberry = new Strawberry (); //This class extends Fruit
Fruit fruit;
fruit = banana;
//or
fruit = strawberry;

这工作得很好。现在我正在 C# 中尝试相同的操作,并且 Fruit 类声明如下:

C# 代码 *****

abstract public class Fruit<T> where T : Fruit<T> {
  //Fruit implementation
}

但是在 C# 中,我无法存储这样的引用:

Fruit fruit; //This gives a compilation error!

无法将香蕉和草莓存储在同一个参考中,我只能这样做:

Fruit<Banana> fruit;
fruit = banana;
//or
Fruit<Strawberry> fruit;
fruit = strawberry;

我想我可以通过添加这样的继承级别来解决它:

abstract public class GenericFruit<T> where T : GenericFruit<T> {}

,然后创建等效的 Fruit 类

abstract public class Fruit : GenericFruit<Fruit>{}

现在从水果中扩展香蕉和草莓,如下所示:

public class Banana : Fruit {}
public class Strawberry : Fruit {}

,然后存储水果引用:

Fruit fruit;
fruit = new Banana();
fruit = new Strawberry();

但这在某种程度上感觉像作弊:(有什么想法吗?我做错了什么吗?

在 C# 中存储泛型类型引用

您遇到的问题是您试图"忘记"(或删除(您创建的某些类型信息。 让我们通过向基类添加一个方法来使示例更具体一些:

public abstract class Fruit<T> where T : Fruit<T>
{
    public abstract T GetSeedless();
}

好的,现在让我们更仔细地看看你要做什么。 让我们假设你可以做你想做的事,并且你有一个水果篮:

Fruit fruit = new Apple();
var seedlessFruit = fruit.GetSeedless();

好的,seedlessFruit的类型是什么? 您可能倾向于说它Fruit这是合理的,但 C# 不允许这样做。 C# 不允许擦除类的泛型参数。 当您声明Fruit<T>谴责所有Fruit都有一个泛型参数时,您无法擦除它。

我认为你

已经接近一个解决方案,但我认为你有点颠倒了。 与其让非泛型FruitGenericFruit<Fruit>继承,不如翻转它,让泛型版本从非泛型版本继承。

我还有一个建议,那就是将非泛型Fruit变成一个接口而不是一个抽象类。 我将演示原因(最终是因为 C# 在重写方法时不允许返回类型协方差;可以肯定的是,这很糟糕(。

public interface IFruit
{
    IFruit GetSeedless();
}
public abstract class Fruit<T> : IFruit where T : Fruit<T>
{
    public abstract T GetSeedless();
    IFruit IFruit.GetSeedless()
    {
        return GetSeedless();
    }
}

我在这里所做的是通过在 Fruit 类中显式实现 IFruit 接口来创建假返回类型协方差。 现在,这允许您在同一引用中存储不同种类的水果,并且仍然使用 GetSeedless 方法:

IFruit fruit = new Apple();
var seedlessFruit = fruit.GetSeedless();

这还允许您在要擦除通用信息时选择哪些方法和属性应该可用。 这些方法中的每一个都可以在基类中显式实现,并"替换"为泛型版本。 这样,如果您确实具有泛型类型信息,则可以使用更具体的类型。

首先,这个:

abstract public class Fruit<T> where T : Fruit<T>

只是行不通,因为你通过说TFruit<T>来创建一个无限循环。(开始用Fruit<T>替换Fruit<T>中的T,你会发现它不可能结束(。

编辑:正如凯尔所说,这是有效的。

解决方案可能是:

abstract public class Fruit
{
    // Generic implementation
}
abstract public class Fruit<T> : Fruit
    where T : Fruit // edit: or Fruit<T>
{
    // Overriding generic implementation
}

你可以有:

public class Banana : Fruit<YourType> // edit: or Fruit<Banana>
{
    // Specific implementation
}

最后,这应该可以很好地工作:

Fruit fruit;
fruit = new Banana();
fruit = new Strawberry();