【问题标题】:Can not use second level derived class as parameter of generic base class type不能使用二级派生类作为泛型基类类型的参数
【发布时间】:2012-08-30 22:54:52
【问题描述】:

我遇到了一个有趣的情况,有些事情正在发挥作用,但有些事情却没有,我不知道为什么。下面是近似我的情况的代码。我在存储库中有一个静态的,它采用由对象的基本类型实现的泛型类型。然后,我有两个基于该泛型类型的派生类。派生类的第一级填充了基类型的泛型参数并且工作正常,但是从填充泛型参数的类派生的任何类都不能代替它派生的基类。

public class Vehicle<TVehicleType, TStorage>
{
}

public class Car : Vehicle<Car, ParkingLot>
{
}

public class PickupTruck : Car
{
}

public class Dealership <TDrivableVehicle>
{
    public static TDrivableVehicle GetVehicle<TVehicleType, TStorage>(TStorage lot)
         where TDrivableVehicle : Vehicle<TVehicleType, TStorage>, new()
    {
    }
}

public class CarDealership : Dealership<Car>
{
    public static Car GetDrivableVehicle(aCarParkingLot)
    {
        return Dealership.GetDrivableVehicle<Car, CarParkingLot>(aCarParkingLot); <-- Works fine
    }
}

public class PickupTruckDealership : CarDealership
{
    public static PickupTruck GetDrivableVehicle(aCarParkingLot)
    {
        return Dealership.GetDrivableVehicle<PickupTruck, CarParkingLot>(aCarParkingLot); <-- fails
    }
}

就 PickupTruck 理解其通用基础而言,某些方面似乎可以正常工作,但扩展(此处未显示)和将类型传递给类型参数特别不能(GetDrivableVehicle 调用)。我的猜测是扩展方法与类型参数问题有关,因为它需要弄清楚类型。

任何想法为什么这不起作用和/或可以做些什么来解决它?

【问题讨论】:

  • 很抱歉,我在您的代码中没有看到任何扩展方法。另外,究竟是哪一部分给了你错误?
  • 我想我理解您遇到的一些问题 - 但您可能希望确保上面的代码仅编译您突出显示的错误。当我粘贴这段代码时,有一些基本错误;尤其是最后两个静态方法中的错误参数说明符;以及您尝试在不传递类型参数的情况下使用 Dealership 的事实。此外 - 您不能将类泛型参数的 where 约束放在其中的方法上 - 它必须继续类型本身。我知道这是一个例子——但它仍然需要“工作”
  • 是的,对不起,我意识到这是一个不完美的例子,但我主要是在寻找更多关于它为什么不工作或什么可能是更好的模式的信息,所以我认为鉴于一个完整的可编译示例的复杂性,它至少可以理解这一点。我认为其他一些 cmets 确实如此,但很抱歉这让它变得更加困难。
  • @Andre - 是的,对不起,我忘了加入扩展方法,但问题也出现在指定的行上。我相信他们因为同样的原因失败了,并且没有一个明确的方法来给出扩展方法的全部范围。我将更改原始问题以删除扩展方法参考

标签: c# generics inheritance


【解决方案1】:

已将您的代码重写到我可以让它在您所说的地方失败的地步 - 问题正如 Thom Smith 所说:PickupTruck 继承 Car,因此是 Vehicle&lt;Car, ParkingLot&gt;,而不是 @ 987654324@。此外,由于它是通用继承,因此不可能是其他任何东西。

我知道您的代码只是您遇到的问题的简化表示 - 但如果它足够接近,那么我们可以在这里对整体架构进行一些有用的观察。

我并不反对泛型基础知道它们的派生对应物——事实上它对工厂特别有用;但是它几乎总是立即排除任何进一步的继承。

您试图在类型级别编码太多信息;除了我们在这里看到的尖括号的数量之外,Vehicle&lt;TVehicleType, TStorage&gt; 正在确定它可以存储在其中的存储类型实际上暗示了一点不协调的性质。

对我来说,这没有任何意义,因为假设我们今天有一个ParkingLot,但明天我们也会得到一个Hangar(用于存放在掩护下的汽车)——这将需要一个全新的包由于我们也将派生类型传递给基类Vehicle&lt;TDerived, ...&gt;,因此不相等的车辆类型 - ergo ParkingLotCarHangarCar 可以永远相等,即使两个实例都代表相同的品牌/型号等。

所以,预料到这一点,你已经去继承,你有一个共同的Car,但当然在这一点上任何继承都是毫无意义的,因为Car 是一个Vehicle&lt;Car,...&gt;,所以从它派生的任何东西也必须是。只有多重继承才可能不是这种情况,但即便如此,它也无法解决整个ParkingLot 问题。

问问自己为什么Vehicle&lt;,&gt; 需要知道派生类型?这样你就可以有一个工厂方法吗?在这种情况下,您应该将其放入DealershipParkingLot 类型中;不是车辆基地:

public interface IVehicle {}
public interface ICar : IVehicle {} //because pickup trucks share some car traits
public interface IPickup : ICar, IVehicle {}
public interface IStorage {}

public class Car : ICar, IVehicle {}
public class Pickup : IPickup, ICar, IVehicle {}

public class ParkingLot : IStorage {}
public class Hangar : IStorage {}

public class Dealership
{
  public static TVehicle GetVehicle<TVehicle>(IStorage storage)
    where TVehicle : IVehicle, new()
  {

  }
}

//now you can specialise if you really need to

public class CarDealership
{
    public static Car GetVehicle(IStorage storage)
    {
        return Dealership.GetVehicle<Car>(storage);
    }
}

public class PickupDealership
{
    public static Pickup GetVehicle(IStorage storage)
    {
        return Dealership.GetVehicle<Pickup>(storage);
    }
}

现在您有了车辆类型之间的运行时关系;允许不同的具体类型共享特征,例如 ICarIPickup 接口上的特征;但是你已经破坏了车辆和它的存储之间的关系,所以你可以从FootballPitch 得到一个IVehicle,或者如果你需要把它从码头开到RiverBed

【讨论】:

  • 感谢您写得非常好。我想我终于明白问题的本质了。唉,这是我继承的代码库,虽然停车场位并不像看起来那么可怕,因为它是一个框架驱动的抽象数据层,所以有一定的灵活性可以将它撕掉并用其他东西替换它它实现了相同的数据表示。也就是说,从头开始,我会尽量避免它。我认为最初的推理与一些 CodeSmith 模板有关,但这早于我的时间。
  • @AJHenderson 啊,是的。我很欣赏这可能不会在您的实际问题方面达到最佳效果,而且听起来您在尝试将其简化为尽可能简单的示例时做了完全正确的事情,所以对您表示敬意。 CodeSmith 模板 - 嗯,是的 - 在 .Net ORM 变得更加成熟之前,它是一项伟大的技术:) 祝你好运!
  • 是的,我们正在努力迁移到 EF,但还有很长的路要走。尝试将我们的功能拆分为更多特定于应用程序域的功能是我们能够突破的第一个可管理的块之一。我相信这将是一个持续的过程,因为我们还有其他一些问题要处理。
  • 另外,你打得很好,我很难理解问题出现在哪里,继承与泛型的行为方式的性质正是我需要的信息。虽然这当然不是我想听到的,但我可以理解为什么该类型不支持派生类型,因为类型修饰不完全匹配。
【解决方案2】:

按照您的设置方式,PickupTruck 不是Vehicle&lt;PickupTruck, CarParkingLot&gt;,而仅仅是Vehicle&lt;Car, CarParkingLot&gt;。这种无限模板递归使用起来可能会非常混乱。您可以通过声明 PickupTruck : Vehicle&lt;PickupTruck, CarParkingLot&gt;、处理协方差或重构类层次结构以避免混淆模式来解决此特定问题。

【讨论】:

  • 这是一个过于模糊实际目的的示例。更详细地解释一下,实际代码是从数据库中加载对象。车辆类型实际上是加载对象的数据库表。这是一个多个应用程序从同一个数据库运行的环境,此代码的目的是从同一个数据层加载到两个或多个应用程序特定框架之一。因此,尽管有额外的继承级别,但类型确实很常见。汽车是常见的,而皮卡是应用程序规范的功能。
【解决方案3】:

您正在尝试使用派生类代替基类。这行不通。您应该能够使用您的 BASE 类而不是更多派生类,而不是相反。您可以将 Car 转换为接口 (ICar) 并让 PickupTruck 实现 ICar。那会奏效。

了解逆变与协方差。 http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

【讨论】:

  • 我认为这里可能需要注意一些 - 正如我的评论所指出的,所提供的代码并没有接近编译,而且它的问题比反/协方差问题更根本。
  • 没错,我同意。可能存在根本的设计缺陷。
  • 也就是说,@RyanBennett,我认为接口确实是一个更好的主意
  • 我一直在阅读关于逆变与协方差的文章,因为我认为它可能与此有关,但我无法理解它是如何应用在这里的。我的理解是 Car 知道它是指定泛型类型的车辆。当作为类型传递给方法时,第一层可以正常工作。我不明白第二层继承是如何破坏它的,因为孙子应该具有与孩子相同的类型装饰,并且孩子工作。如果这是一个协变/逆变问题,那么继承不应该也不起作用吗?
猜你喜欢
  • 2012-09-08
  • 2014-03-22
  • 2011-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多