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;

如您所见,我有一个可以包含许多DataSourcesDictionary,无论子类型如何。
但是最后一行给出了下一个编译错误:

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]'.

C# - 使用变量类型将子类强制转换为父类

你需要一个协变量接口才能正常工作

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 仅用作方法的返回类型或只读属性(即它仅从接口中出现并且从未插入)时,才应使用协方差。

编辑

根据您的编辑,您现在有两种通用类型 TT1 。 您已将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;