【问题标题】:C# - Cast child class to parent with variable typeC# - 将子类转换为具有变量类型的父类
【发布时间】:2016-03-02 15:57:56
【问题描述】:

我遇到了一个意外问题。

我想将具有特殊类型的子类转换为具有泛型类型的父类,但编译器不希望这种情况发生...

这是我的父类:

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

【问题讨论】:

  • 那是因为它不是DataSource&lt;Object&gt;,而是DataSource&lt;Row&gt;。如果您想要协方差,则必须使用接口而不是类。
  • 但是我的DataSource 类中有一个通用构造函数,所以没有冗余代码,这对于接口是不可能的。
  • 你仍然可以在接口和子类之间有一个抽象类。只是协方差仅在您使用接口引用时才有效。 msdn.microsoft.com/en-us/library/dd997386.aspx
  • 你为什么需要投射?
  • 因为否则我无法将XlsxDataSource 对象添加到只接受DataSource&lt;Object&gt; 对象的Dictionary...当我尝试时,这是我得到的错误:Cannot implicitly convert type 'EDS.Models.XlsxDataSource' to 'EDS.Models.DataSource&lt;object&gt;'

标签: c# casting


【解决方案1】:

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

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 的逆变式,这对于您使用该类型的方式是正确的,但是逆变式仅允许您分配给派生程度更高的类型,而不是派生程度更低的类型。此外,您根本没有将T 设置为变体,尽管如果您将返回类型更改为IEnumerable&lt;T&gt;,它可能是协变的。这是您可以执行的操作。

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&lt;Row&gt;,它可以分配给IEnumerable&lt;object&gt;,因为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,这将导致在铸造异常中。所以最终一个更好的问题是为什么字典需要在显然不是协变的东西上降低泛型类型。

【讨论】:

  • 感谢 sn-p,我正在研究如何实现协变接口 :)
  • 我尝试了您的解决方案,但没有成功,请您检查我的编辑吗?
  • @Renlidacha 我已经根据您的编辑更新了我的答案。但基本上你尝试的方法不起作用,因为它在两种泛型类型上都不是协变的。
  • 非常感谢。重点是我要管理好几种类型的DataSource,比如Excel文件、Acces文件等。由于每个数据源的方法名称相同,但实现不同,我需要能够将特定数据源(例如 XslxDataSource)转换为通用数据源,因为我不知道运行时数据源的类型是什么。
【解决方案2】:

这是因为 C# 中的泛型类不是协变的。

协方差使您可以使用比最初指定更多的派生类型。

在 C# 中,接口中可能存在协变,因此如果您可以将 DataSource&lt;T&gt; 类转换为接口,则可能的解决方案是:

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;

【讨论】:

  • 这是逆变式。您需要使用out 进行协方差。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-06
  • 2022-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多