【问题标题】:Argument not assignable due to contravariance由于逆变,参数不可赋值
【发布时间】:2021-04-03 01:12:45
【问题描述】:

我正在尝试编写一个可以在不同类型之间进行转换的简单库。使用 .Net Core 3.1。

这样做的目的是在继承自同一个基类的两个类之间进行转换。

public class SharedBaseClass {}

public class DestinationClass : SharedBaseClass {
    public DestinationClass(){}
}

public class InputClass : SharedBaseClass {
    public InputClass(){}
}

因此,我已经介绍了一个定义这种转换器的接口

public interface IConverter<out TU> where TU : SharedBaseClass
{
    public TU Convert(SharedBaseClass input);
}

然后下面的类使用这个接口来执行转换

public class ConverterExecutor
{
    private readonly Dictionary<Type, IConverter<SharedBaseClass>> _converters;
    
    public ConverterExecutor(Dictionary<Type, IConverter<SharedBaseClass>> converters)
    {
        _converters = converters;
    }
    
    public IEnumerable<SharedBaseClass> ConvertMultiple(IEnumerable<SharedBaseClass> classesToConvert)
    {
        var converted = new List<SharedBaseClass>();
        
        foreach(var toConvert in classesToConvert)
        {
            _converters.TryGetValue(toConvert.GetType(), out var converter);
            
            if (converter != null) {
                converted.Add(converter.Convert(toConvert));
                continue;
            }
                              
            converted.Add(toConvert);
        }
        
        return converted;
    }
}

然后,客户端将简单地创建 IConverter 接口的实现来封装转换逻辑。从InputClassDestinationClass 的转换器示例是

public class DestinationConverter: IConverter<DestinationClass> {
    public DestinationClass Convert(SharedBaseClass input) {
        return new DestinationClass();
    }
}

为了完成示例,我添加了一个简短的 main 方法来说明如何设置这些方法

public class Program
{
    public static void Main()
    {
        var executor = new ConverterExecutor(new Dictionary<Type, IConverter<SharedBaseClass>>{
            // various converters for various types added here
            {typeof(InputClass), new DestinationConverter()}
        });
        
        var result = executor.ConvertMultiple(new List<SharedBaseClass>{new InputClass()});
        Console.WriteLine(result.First());
    }
}

这一切都有效,但是我对 IConverter 实现的 convert 方法的输入参数依赖于基类这一事实感到困扰。

public DestinationClass Convert(SharedBaseClass input)

为了解决这个问题,我尝试重新定义界面:

public interface IConverter<out TU, in T> where TU : SharedBaseClass where T: SharedBaseClass
{
    public TU Convert(T input);
}

此重构在所涉及的类中运行良好,并在每个实现中为我提供了正确的类型,但是在 main 方法中出现编译错误,因为 DestinationConverter 类的签名不适合添加到字典中。我怀疑这是因为T: SharedBaseClass 参数已作为in 参数(逆变)添加到IConverter 接口上,但是将其保留为不变量以同样的方式失败。我怀疑它是否是协变的(使用输入参数不可能),编译器会允许这样做。我想我的抽象在某个地方弄错了,那么在这种情况下,什么是合适的解决方案?

public class Program
{
    public static void Main()
    {
        var executor = new ConverterExecutor(new Dictionary<Type, IConverter<SharedBaseClass, SharedBaseClass>>{
            // various converters for various types to be added here
            {typeof(InputClass), new DestinationConverter()}
        });
        
        var result = executor.ConvertMultiple(new List<SharedBaseClass>{new InputClass()});
        Console.WriteLine(result.First());
    }
}

失败的完整重构示例:https://dotnetfiddle.net/vuDCiH

【问题讨论】:

标签: c# .net .net-core covariance abstraction


【解决方案1】:

问题的根源在于您可以从字典中获取任何转换器,并将其提供给您的 SharedBaseClass 实例进行转换。因此,您的字典中的转换器需要声明为接受SharedBaseClass

如果您的字典接受采用 SharedBaseClass 实例的转换器,那么进入它的所有转换器也必须能够采用 SharedBaseClass 实例进行转换,因为您在技术上能够获取其中任何一个字典,并给它任何SharedBaseClass 实例。

因此,任何前进的道路都取决于我们能否摆脱包含IConverter&lt;SharedBaseClass, SharedBaseClass&gt; 实例的字典。一种可能的方法是:

public class ConverterExecutor
{
    private readonly Dictionary<Type, Func<SharedBaseClass, SharedBaseClass>> _converters = new();
    
    public void RegisterConverter<TU, T>(IConverter<TU, T> converter) where TU : SharedBaseClass where T : SharedBaseClass
    {
        _converters[typeof(T)] = x => converter.Convert((T)x);  
    }
    
    public IEnumerable<SharedBaseClass> ConvertMultiple(IEnumerable<SharedBaseClass> classesToConvert)
    {
        var converted = new List<SharedBaseClass>();
        
        foreach(var toConvert in classesToConvert)
        {
            _converters.TryGetValue(toConvert.GetType(), out var converter);
            
            if (converter != null) {
                converted.Add(converter(toConvert));
                continue;
            }
                              
            converted.Add(toConvert);
        }
        
        return converted;
    }
}

然后:

var executor = new ConverterExecutor();
executor.RegisterConverter(new DestinationConverter());

Link.

我们已将那些 IConverter&lt;SharedBaseClass, SharedBaseClass&gt; 实例替换为接受 SharedBaseClass 并返回 SharedBaseClass 的委托。每个委托持有一个转换器,并将SharedBaseClass 实例转换为转换器期望的类型。

现在,如果您将错误的类型传递给特定转换器,您会收到 InvalidCastException:问题并没有真正消失,但我们已将检查从编译时移至运行时。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-01
    • 2013-04-10
    相关资源
    最近更新 更多