简体   繁体   English

如何使用接口剥离C#中的泛型?

[英]How to use an interface to strip a generic in C#?

I currently have an inheritance structure where the return type of a method is being left as an open generic so that each class can return an object from the corresponding level of another structure. 我目前有一个继承结构,其中方法的返回类型保留为开放的泛型,以便每个类都可以从另一结构的相应级别返回对象。 For example, lets say we had a VehicleFactory where TVehicle:Vehicle that could ProduceVehicle TVehicle. 例如,假设我们有一个VehicleFactory,其中可以生产TVehicle TVehicle的TVehicle:Vehicle。 I also have CarFactory:VehicleFactory where TVehicle:Car. 我也有CarFactory:VehicleFactory,其中TVehicle:Car。

Car inherits from Vehicle, so all this is valid and allows me to know that my CarFactory must produce Cars and my Vehicle factory may produce any kind of vehicle. 汽车继承自汽车,因此所有这些都是有效的,并使我知道我的汽车制造厂必须生产汽车,而我的汽车制造厂可以生产任何类型的汽车。 The problem I have run in to is that I need a way to instantiate a VehicleFactory as a CarFactory when it is being run by Ford, but as a BoatFactory when being run by Wave Runner. 我遇到的问题是,我需要一种方法来将VehicleFactory实例化为由福特运行时的CarFactory,而将其实例化为由Wave Runner运行时的BoatFactory。

I thought I could do this by creating an interface that matched the functionality of VehicleFactory and writing a MakeFactory method that returns an IVehicleFactory (which would return vehicles non-genericly). 我以为可以通过创建与VehicleFactory功能匹配的接口并编写一个返回IVehicleFactory的MakeFactory方法来实现此目的(该方法将非通用地返回车辆)。 Since CarFactory returns cars, which are vehicles, it fulfills the interface and all should be right in the world. 由于CarFactory归还汽车(即车辆),因此它满足了界面的要求,并且在世界范围内都应正确无误。 The unexpected problem is that VehicleFactory fails to meet the interface that is closed as TVehicle being Vehicle, despite the fact that TVehicle must be a Vehicle. 出乎意料的问题是,尽管TVehicle必须是Vehicle,但VehicleFactory无法满足以TVehicle为Vehicle的方式关闭的接口。

Does anyone know why this is or if there is another way to work around this limitation? 有谁知道这是为什么,或者是否有其他方法可以解决此限制? If there isn't a way around this limitation directly, are there any alternative methods for having a shared set of functionality ensure that it is always instantiated as one of two or more sets of more specific classes. 如果没有直接解决此限制的方法,那么是否有任何其他方法可用于拥有一组共享功能,以确保始终将其实例化为两组或更多组更具体的类之一。 (Where Vehicle is the shared layer and Car and Boat are the context specific layers.) (其中Vehicle是共享层,而Car和Boat是上下文特定的层。)

class Vehicle
{
}

class Car : Vehicle
{
}

interface IVehicleFactory
{
     Vehicle ProduceVehicle();
}

class VehicleFactory<TVehicle> : IVehicleFactory
    where TVehicle:Vehicle
{
    public virtual TVehicle ProduceVehicle()
    {
    }
}

class CarFactory<TVehicle> : VehicleFactory<TVehicle>
    where TVehicle : Car
{
    public override TVehicle ProduceVehicle()
    {
    }
}

And the code using it 和使用它的代码

static IVehicleFactory CreateVehicleFactory()
{
    if(somecondition)
    {
         Return new CarFactory<Car>();
    }
    else
    {
         Return new BoatFactory<Boat>();
    }
}

Adding some more detail to clarify the problem. 添加更多详细信息以澄清问题。

The use of the term factory is not intended to imply a factory pattern. 使用术语工厂并不意味着暗示工厂模式。 It is actually a repository that is retrieving a "vehicle." 它实际上是一个正在检索“车辆”的存储库。 The type of vehicle is an application specific version of a library with a common shared base code. 车辆类型是具有通用共享基本代码的库的特定于应用程序的版本。 The repositories for each application Car and Boat may have different logic for the same retrieval and may be dependent on fields in the Car or Boat variant of their object. 每个应用程序Car和Boat的存储库对于相同的检索可能具有不同的逻辑,并且可能取决于其对象的Car或Boat变体中的字段。 I need a way to build a factory which can look at the context of the application I am in and return the appropriate repository (BoatFactory or CarFactory), such that the shared code at the Vehicle level will properly use any application specific overridden functionality. 我需要一种构建工厂的方法,该工厂可以查看我所在的应用程序的上下文并返回适当的存储库(BoatFactory或CarFactory),以使Vehicle级别的共享代码可以正确使用任何应用程序特定的重写功能。

Hope this help: 希望对您有所帮助:

interface IVehicleFactory<out T> where T : Vehicle
{
    T Create();
}

class CarFactory : IVehicleFactory<Car>
{
    public Car Create()
    {
        return new Car();
    }
}

class VehicleFactoryProvider
{
    public IVehicleFactory<T> GetFactoryFor<T>() where T : Vehicle
    {
        throw new NotImplementedException();
    }
}

class Program
{
    public static void Main(string[] args)
    {
        VehicleFactoryProvider provider = new VehicleFactoryProvider();
        var factory = provider.GetFactoryFor<Car>();
        var car = factory.Create();
    }
}

Your VehicleFactory<TVehicle> does not satisfy the IVehicleFactory interface. 您的VehicleFactory<TVehicle>不满足IVehicleFactory接口。 To satisfy it, it must have a method with the signature Vehicle ProduceVehicle() . 要满足此要求,它必须具有签名为Vehicle ProduceVehicle() You obviously don't have a method with that signature (but returns a derived type) so it will not work. 您显然没有带有该签名的方法(但是返回派生类型),因此它将不起作用。

The easiest way to deal with this would be to explicitly implement the interface and return the result of calling your other method. 解决此问题的最简单方法是显式实现接口并返回调用其他方法的结果。 The interface will be satisfied and you're still producing a vehicle of the correct type. 界面将得到满足,您仍在生产正确类型的车辆。 This is similar to what you would usually do when implementing the IEnumerable<T> interface (which requires implementing the IEnumerable interface). 这类似于在实现IEnumerable<T>接口(需要实现IEnumerable接口)时通常会执行的操作。

class VehicleFactory<TVehicle> : IVehicleFactory
    where TVehicle:Vehicle
{
    public virtual TVehicle ProduceVehicle()
    {
        throw new NotImplementedException();
    }

    // explicitly implement the interface by
    // returning the value of our actual method
    Vehicle IVehicleFactory.ProduceVehicle()
    {
        return ProduceVehicle();
    }
}

I'm thinking that perhaps a small refactor of architecture might be in order here; 我在想,也许可以在这里对建筑进行一小部分重构。 semantically, it seems like all you need is: 从语义上来说,您似乎需要的只是:

public class VehicleFactory
{
    protected Dictionary<Type, ISpecificTransportFactory> _mapping = // initialize

    public Vehicle CreateVehicle<TVehicle>() // any params
    {
        if(_mapping.ContainsKey(typeof(TVehicle))
          return _mapping[typeof(TVehicle)].CreateVehicle(); // any params that need to be passed

        throw new ConfigurationInvalidException("No transportFactory defined for : " + typeof(TVehicle).Name);
    }
}

Then with a dash of this: 然后加上一点点:

public interface ISpecificTransportFactory
{
     Vehicle CreateVehicle();
}

public abstract class FactoryBase<TVehicleType> : ISpecificTransportFactory
    where TVehicleType : Vehicle
{
    public Vehicle CreateVehicle()
    {
        return CreateInstance();
    }

    public abstract TVehicleType CreateInstance();
}

public class CarFactory : FactoryBase<Car>
{
     public override Car CreateInstance()
     {
          return new Car();
     }
}

Theoretically, then you can use it in the following fashion: 从理论上讲,您可以按以下方式使用它:

public static void Main(string[] args)
{
     var vehicleFactory = new VehicleFactory();

     var vehicle = vehicleFactory.CreateVehicle<Car>();
}

Well, while I don't have a solution to how to make it work yet, (at least not in full), I think I now understand the problem. 好吧,虽然我还没有解决方案(至少不能完全解决),但我认为我现在已经明白了问题所在。 One of my initial assumptions appears to have been incorrect. 我最初的假设之一似乎是错误的。 I was under the impression that a less specific return type on an interface could be fulfilled by a more specific type. 我的印象是,接口上的特定返回类型可以由更特定的类型实现。 ie, an interface defining SomeBaseClass as a return type for a method can not be implemented by a class that implements the method returning SomeChildClass where SomeChildClass inherits from SomeBaseClass. 也就是说,实现返回方法SomeChildClass的类(实现SomeChildClass继承自SomeBaseClass的类)不能实现将SomeBaseClass定义为方法的返回类型的接口。

The use of covariant generics (via the out keyword) does provide some manner of ability to circumvent this by allowing the interface to be generically defined, however for my purposes, this falls apart when I hit results that need to be IList based (which requires an invariant type parameter). 协变泛型的使用(通过out关键字)确实通过允许对接口进行泛型定义而提供了某种方式来规避此问题,但是出于我的目的,当我遇到需要基于IList的结果时,这会分崩离析(这需要不变类型参数)。

Ultimately, the best it appears that can be done is that I can make it so that CarFactory only understands that it is working with a vehicle (by removing the additional where condition on the TVehicle generic parameter), requiring a cast within CarFactory, however when ever CarCompany instantiates CarFactory, it can still make it as CarFactory and the client code itself will not have to worry about a cast. 最终,似乎可以做到的最好的是,我可以做到这一点,以便CarFactory仅了解它正在与车辆一起工作(通过删除TVehicle通用参数上的附加where条件),需要在CarFactory中进行强制转换,但是当即使CarCompany实例化CarFactory,它仍然可以像CarFactory一样实现,并且客户端代码本身不必担心强制转换。

It's not ideal, but I think it may be the best possible due to C#'s lack of covariant return types. 这并不理想,但由于C#缺少协变返回类型,因此我认为这可能是最好的选择。

interface IVehicleFactory<TVehicle> where TVehicle:Vehicle
{
    TVehicle SomeMethod();
}

VehicleFactory<TVehicle> : IVehilceFactory<TVehicle> where TVehicle:Vehicle
{
    TVehicle SomeMethod()
    {
    }
}

CarFactory<TVehicle> : IVehicleFactory<TVehicle> where TVehicle:Vehicle
{
    TVehicle SomeMethod()
    {
       ~~Always returns car and uses cast from Vehicle when necessary.
    }
}

class VehicleFactory<TVehicle>
{
   static IVehicleFactory<TVehicle> CreateContextSpecificFactory()
   {
       if(someCondition)
       {
            return new CarFactory<TVehicle>;
       }
   }
}

class CarCompany
{
    CarFactory<Car> carFactory = new CarFactory<Car>;
    Car car = carFactory.SomeMethod();
}

class VehicleCompany
{
    VehicleFactory<Vehicle> vehicleFactory = VehicleFactory<Vehicle>.CreateContextSpecificFactory();
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM