由于逆变而无法分配参数
Argument not assignable due to contravariance
我正在尝试编写一个可以在不同类型之间进行转换的简单库。使用 .Net Core 3.1.
这样做的目的是在两个继承自同一基础 class 的两个 class 之间进行转换 class。
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);
}
下面的 class 使用此接口执行转换
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 接口的实现来封装转换逻辑。从 InputClass
到 DestinationClass
的转换器示例是
public class DestinationConverter: IConverter<DestinationClass> {
public DestinationClass Convert(SharedBaseClass input) {
return new DestinationClass();
}
}
为了完成示例,我添加了一个简短的主要方法来说明如何设置这些方法
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 实现的转换方法的输入参数依赖于基础 class。
public DestinationClass Convert(SharedBaseClass input)
为了解决这个问题,我尝试重新定义接口:
public interface IConverter<out TU, in T> where TU : SharedBaseClass where T: SharedBaseClass
{
public TU Convert(T input);
}
此重构在所涉及的 classes 中工作正常,并在每个实现中为我提供正确的类型,但是我在主要方法中遇到编译错误作为 DestinationConverter class 的签名不适合加入字典。我怀疑这是因为 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
问题的根源在于您可以从字典中获取任何转换器,然后将您的 SharedBaseClass
实例提供给它进行转换。因此,您字典中的转换器需要声明为接受 SharedBaseClass
.
如果您的字典接受采用 SharedBaseClass
实例的转换器,那么进入其中的所有转换器也必须能够采用 SharedBaseClass
实例进行转换,因为您在技术上能够从字典中取出其中任何一个,并给它任何 SharedBaseClass
个实例。
因此,任何前进的方式都取决于我们能否摆脱包含 IConverter<SharedBaseClass, SharedBaseClass>
个实例的字典。一种可能的方法是:
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<SharedBaseClass, SharedBaseClass>
实例替换为采用 SharedBaseClass
和 return 和 SharedBaseClass
的委托。每个委托持有一个转换器,并将 SharedBaseClass
实例转换为转换器期望的类型。
现在,如果您将错误的类型传递给特定的转换器,您会得到一个 InvalidCastException
:问题并没有真正消失,但我们已将检查从编译时转移到运行时。
我正在尝试编写一个可以在不同类型之间进行转换的简单库。使用 .Net Core 3.1.
这样做的目的是在两个继承自同一基础 class 的两个 class 之间进行转换 class。
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);
}
下面的 class 使用此接口执行转换
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 接口的实现来封装转换逻辑。从 InputClass
到 DestinationClass
的转换器示例是
public class DestinationConverter: IConverter<DestinationClass> {
public DestinationClass Convert(SharedBaseClass input) {
return new DestinationClass();
}
}
为了完成示例,我添加了一个简短的主要方法来说明如何设置这些方法
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 实现的转换方法的输入参数依赖于基础 class。
public DestinationClass Convert(SharedBaseClass input)
为了解决这个问题,我尝试重新定义接口:
public interface IConverter<out TU, in T> where TU : SharedBaseClass where T: SharedBaseClass
{
public TU Convert(T input);
}
此重构在所涉及的 classes 中工作正常,并在每个实现中为我提供正确的类型,但是我在主要方法中遇到编译错误作为 DestinationConverter class 的签名不适合加入字典。我怀疑这是因为 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
问题的根源在于您可以从字典中获取任何转换器,然后将您的 SharedBaseClass
实例提供给它进行转换。因此,您字典中的转换器需要声明为接受 SharedBaseClass
.
如果您的字典接受采用 SharedBaseClass
实例的转换器,那么进入其中的所有转换器也必须能够采用 SharedBaseClass
实例进行转换,因为您在技术上能够从字典中取出其中任何一个,并给它任何 SharedBaseClass
个实例。
因此,任何前进的方式都取决于我们能否摆脱包含 IConverter<SharedBaseClass, SharedBaseClass>
个实例的字典。一种可能的方法是:
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<SharedBaseClass, SharedBaseClass>
实例替换为采用 SharedBaseClass
和 return 和 SharedBaseClass
的委托。每个委托持有一个转换器,并将 SharedBaseClass
实例转换为转换器期望的类型。
现在,如果您将错误的类型传递给特定的转换器,您会得到一个 InvalidCastException
:问题并没有真正消失,但我们已将检查从编译时转移到运行时。