我遇到了意想不到的问题。
我想将具有特殊类型的子类强制转换为具有泛型类型的父类,但编译器不希望这种情况发生......
这是我的父类:
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;
正如您所看到的,无论子类型如何,我都有一个Dictionary
可以包含多个DataSources
。
但最后一行是给出了下一个编译错误:
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]'.
答案 0 :(得分:3)
您需要一个共变式界面才能实现此目的
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
的反变量,这对于您使用该类型的方式是正确的,但是反差方法仅允许您分配更多派生类型而不是更少派生类型。此外,您没有将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<Row>
,该IEnumerable<object>
可分配给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
但是现在你已经失去了强类型,这意味着你不知道接口方法返回的列表中对象的实际类型,更糟糕的是你可以将对象传递给{{ 1}}将导致转换异常。因此,最终一个更好的问题是,为什么字典需要将泛型类型降级为明显不同变量的东西。
答案 1 :(得分:1)
发生了这个错误,因为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;