C# - 使用变量类型将子类强制转换为父类
本文关键字:转换 父类 子类 变量 类型 | 更新日期: 2023-09-27 18:37:07
我遇到了一个意外的问题。
我想将具有特殊类型的子类转换为具有泛型类型的父类,但编译器不希望发生这种情况......
这是我的家长类:
public abstract class DataSource<T>
{
// Abstract methods and constructor
}
其中一个子类:
public class XlsxDataSource : DataSource<Row>
{
// Overriden methods and constructor
}
以及给出错误的代码:
XlsxDataSource dataSource = new XlsxDataSource();
var dataSources = new Dictionary<long, DataSource<Object>>();
dataSources[dataSource.id] = (DataSource<Object>) dataSource;
如您所见,我有一个可以包含许多DataSources
的Dictionary
,无论子类型如何。
但是最后一行给出了下一个编译错误:
Cannot convert type 'EDS.Models.XlsxDataSource' to 'EDS.Models.DataSource<object>'
我不明白为什么,因为XlsxDataSource
是来自DataSource
的孩子,而且XlsxDataSource
中的类型明确Row
显然正在实现System.Object
.
编辑
协变量界面在我的情况下不起作用。
这是我修改后的代码:
public interface IDataSource<T, in T1>
{
List<T> GetAllRows();
List<Object> GetValues(T1 row);
}
抽象类:
public abstract class DataSource<T, T1> : IDataSource<T, T1>
{
public abstract List<T> GetAllRows();
public abstract List<Object> GetValues(T1 row);
}
最后是派生较少的类:
public class XlsxDataSource : DataSource<Row, Row>, IDataSource<Row, Row>
{
public abstract List<Row> GetAllRows();
public abstract List<Object> GetValues(Row row);
}
这是选角:
IDataSource<Object, Object> datasource = (IDataSource<Object, Object>) new XlsxDataSource();
但是将XlsxDataSource
转换为IDataSource
对象会导致InvalidCastException
:
Unable to cast object of type 'EDS.Models.XlsxDataSource' to type 'EDS.Models.IDataSource`2[System.Object,System.Object]'.
你需要一个协变量接口才能正常工作
public interface IDataSource<out T> {}
public abstract class DataSource<T> : IDataSource<T> {}
public class XlsxDataSource : DataSource<Row> {}
将允许以下内容
XlsxDataSource dataSource = new XlsxDataSource();
var dataSources = new Dictionary<long, IDataSource<Object>>();
dataSources[dataSource.id] = (IDataSource<Object>) dataSource;
但请注意,仅当类型 T
仅用作方法的返回类型或只读属性(即它仅从接口中出现并且从未插入)时,才应使用协方差。
编辑
根据您的编辑,您现在有两种通用类型 T
和 T1
。 您已将T1
定义为in
contra-vari,这对于您使用该类型的方式是正确的,但是逆方差仅允许您分配给派生程度较高的类型,而不允许分配给派生较少的类型。 此外,您根本没有将T
设置为变体,尽管如果您将返回类型更改为 IEnumerable<T>
,它可能是协变的。 以下是您可以执行的操作。
public interface IDataSource<out T, in T1>
{
IEnumerable<T> GetAllRows();
List<Object> GetValues(T1 row);
}
public abstract class DataSource<T, T1> : IDataSource<T, T1>
{
public abstract IEnumerable<T> GetAllRows();
public abstract List<Object> GetValues(T1 row);
}
public class XlsxDataSource : DataSource<Row, Row>, IDataSource<Row, Row>
{
public abstract IEnumerable<Row> GetAllRows();
public abstract List<Object> GetValues(Row row);
}
public class DerivedRow : Row {}
这将允许你做一些类似的事情
XlsxDataSource dataSource = new XlsxDataSource();
IDataSource<object, DerivedRow> interfaceReference = dataSource;
您可以分配它以允许更多派生类型的T1
,因为您要将它们传递到接口中。 所以方法GetValues(Row row)
可以取DerivedRow
,但不能取object
。 然后,对于T1
,您可以分配给派生类型较少的类型,因为您将从接口中获取这些值。 因此,该方法GetAllRows
返回一个可分配给IEnumerable<object>
的IEnumerable<Row>
IEnumerable
因为它是协变的,并且object
的派生次数少于Row
。
现在你可以做的是创建一个非通用接口来处理这个问题。
public interface IDataSource
{
List<object> GetAllRows();
List<object> GetValues(object row);
}
public abstract class DataSource<T> : IDataSource
{
public abstract List<T> GetAllRows();
List<object> IDataSource.GetValues(object row)
{
return GetValues((T)row);
}
public abstract List<object> GetValues(T row);
List<object> IDataSource.GetAllRows()
{
return GetAllRows().Cast<object>().ToList();
}
}
public class XlsxDataSource : DataSource<Row>
{
public override List<object> GetValues(Row row)
{
throw new NotImplementedException();
}
public override List<Row> GetAllRows()
{
throw new NotImplementedException();
}
}
这将允许你做
XlsxDataSource dataSource = new XlsxDataSource();
var dataSources = new Dictionary<long, IDataSource>();
dataSources[5] = dataSource;
但是现在您已经失去了强类型,这意味着您不知道接口方法返回的列表中对象的实际类型,更糟糕的是,您可能会将对象传递给GetValues
这将导致强制转换异常。 因此,最终一个更好的问题是,为什么字典需要在明显不是协变的东西上降低泛型类型的等级。
出现此错误是因为 C# 中的泛型类不是协变的。
协方差使您能够使用比最初指定的派生类型更多的派生类型。
在 C# 中,接口中可能存在协方差,因此如果可以将类转换为接口DataSource<T>
则可能的解决方案为:
public interface IDataSource<out T>
{
...
}
public abstract class DataSource<T> : IDataSource<T>
{
...
}
public class XlsxDataSource : DataSource<Row>
{
// Overriden methods and constructor
}
并像这样使用字典:
XlsxDataSource dataSource = new XlsxDataSource();
var dataSources = new Dictionary<long, IDataSource<Object>>();
dataSources[dataSource.id] = (IDataSource<Object>)dataSource;