如何让内部成员成为Co-variance?

How to make Co-variance possible for internal members?

主题

这是实施 工厂模式 的简化示例,其方式是 限制对构造函数的直接访问 并防止 从其他程序集创建 object 的非法 种方法。

换句话说,我想限制其他开发人员只能使用这个工厂创建他们的 object。 (在 object 创建期间必须完成一些事情,这些事情并不特定于一个 object,而是与所有 object 相关,因此是 的原因)


解决方案

我有这个委托,用于在我的工厂内创建 object 个实例。

internal delegate T Instantiate<out T>(/*parameters*/) where T : Vehicle;

如您所见,这个委托是内部的,所有与 Vehicle 及其 sub-types 相关的构造函数也是内部的。

internal interface IVehicle{/*...*/}
public class Vehicle
{
    //constructor is not protected. so this cant be intertied from outside assembly.
    internal Vehicle(/*parameters*/){ }
}
public class Car : Vehicle
{
    internal Car(/*parameters*/) : base(/*parameters*/) { }
}

技巧来了。

/// <summary>
/// Choose from one of predefined containers to create your vehicle from factoy.
/// </summary>
/// <typeparam name="T">type of vehicle</typeparam>
public class InstanceContainer<T> where T : Vehicle
{
    internal InstanceContainer(Instantiate<T> instance) // internal
    {
        GetInstance = instance;
    }

    // delegate which creates new instance of T
    internal Instantiate<T> GetInstance { get; } 
}

这使得其他人能够选择他们的委托人但不能访问实际的委托人。这里有一些他们可以选择的选项。

/// <summary>
/// Choose this to create car from factory.
/// </summary>
public static InstanceContainer<Car> CarInstance { get; } = new InstanceContainer<Car>(CreateCar);

private static readonly Instantiate<Car> CreateCar = (/*parameters*/) => new Car(/*parameters*/);

// ... others options like Plane, Ship etc

例子

这是从工厂创建 object 的方法之一。

public static T CreateVehicle<T>(/*parameters,*/ InstanceContainer<T> instance) where T : Vehicle
{
    T obj;
    //...
    // At some point:
        obj = instance.GetInstance(/*parameters*/);
    //...
    return obj;
}

下面是如何使用它。 (来自其他程序集 说创建一个 Car

VehicleFactory.CreateVehicle(/*parameters,*/ VehicleFactory.CarInstance);

这很好用,合规性很好,到目前为止运行良好。

注意:VehicleFactory 是 public 静态 class 持有实例、方法、委托...


问题

现在我想做 InstanceContainer co-variant 这样我就可以抽象地传递实例了。

// turns long name into short one!
using VehicleInstance = Factories.VehicleFactory.InstanceContainer<Factories.Goods.Vehicle>

//...
//...

VehicleInstance instance;
// instance will be choosen based on some conditions. like this:
if(true)
    instance = VehicleFactory.CarInstance; // we got magic co-variant here!
else
    instance = VehicleFactory.PlaneInstance;

VehicleFactory.CreateVehicle(/*parameters,*/ instance);

有什么方法可以使 InstanceContainer co-variant 但不将其委托暴露给其他程序集吗?

这是我目前尝试过的方法:

public interface IContainer<out T>
{
    // empty!
}

internal class InstanceContainer<T> : IContainer<T> where T : Vehicle
{
    internal InstanceContainer(Instance<T> instance)
    {
        GetInstance = instance;
    }

    internal Instance<T> GetInstance { get; }
}

这次IContainer是public接口,InstanceContainer是内部。这意味着我们必须使用 IContainer 而不是 安全访问 。小事情必须改变:

// works fine!
public static IContainer<Car> CarInstance { get; } = new InstanceContainer<Car>(CreateCar);

public static T CreateVehicle<T>(/*parameters,*/ IContainer<T> instance)
{
    T obj;
    //...
    // At some point:
        obj = ((InstanceContainer<T>)instance).GetInstance(/*parameters*/); // runtime error.
    //...
    return obj;
}

问题与结论

问题出现在运行时。我们不能将 co-variant 接口转换回其实际的 class 实现。

抱歉,问题很长。但我知道没有主题标题的答案会很简单:

"You cant. because generic variants are pinned to interfaces. interfaces are pinned to public implements. it cant be internal. that's c# limit thing :("

我也尝试过显式实现,但这也不起作用。你基本上以 public 接口结束,无论如何都会暴露内部委托。

那么有更好的方法来实现这种工厂模式还是我使用了错误的设计模式?也许我想多了,解决办法很简单。

感谢您花时间回答。

我会简化你的解决方案:

internal delegate Vehicle VehicleConstructor(string data, string type);

public static class Factory {
    private static readonly Dictionary<Type, VehicleConstructor> _factories = new Dictionary<Type, VehicleConstructor>() {
        {typeof(Car), (data, type) => new Car(data)},
        {typeof(Plane), (data, type) => new Plane(data, type)}
    };

    public static T CreateVehicle<T>(string data, string type) where T : Vehicle {
        if (!_factories.ContainsKey(typeof(T)))
            throw new Exception("Cannot create Vehicle of type " + typeof(T).Name);
        return (T) _factories[typeof(T)](data, type);
    }

    public static Vehicle CreateVehicle(Type vehicleType, string data, string type) {
        if (!_factories.ContainsKey(vehicleType))
            throw new Exception("Cannot create Vehicle of type " + vehicleType.Name);
        return _factories[vehicleType](data, type);
    }
}

那么您可以同时执行以下操作:

var car = Factory.CreateVehicle<Car>("data", "type");

还有这个:

var v = Factory.CreateVehicle(true ? typeof(Car) : typeof(Plane), "data", "type");