简体   繁体   中英

C# - Cast child class to parent with variable type

I'm experiencing an unexpected issue.

I would like to cast a child class with a special type to his parent class with a generic type but the compiler do not want this to happen...

Here is my parent class:

public abstract class DataSource<T>
{
    // Abstract methods and constructor
}

One of the child classes:

public class XlsxDataSource : DataSource<Row>
{
    // Overriden methods and constructor
}

And the code giving the error:

XlsxDataSource dataSource = new XlsxDataSource();

var dataSources = new Dictionary<long, DataSource<Object>>();

dataSources[dataSource.id] = (DataSource<Object>) dataSource;

As you can see, I have a Dictionary that can contain many DataSources regardless of the child type.
But the last line is giving the next compile error:

Cannot convert type 'EDS.Models.XlsxDataSource' to 'EDS.Models.DataSource<object>'

And I don't get why, as XlsxDataSource is a child from DataSource and the type in XlsxDataSource is explicitly Row wich is obviously implementing System.Object .

Edit

The co-variant interface is not working in my case.
Here is my modified code:

public interface IDataSource<T, in T1>
{
    List<T> GetAllRows();

    List<Object> GetValues(T1 row); 
}

The abstract class:

public abstract class DataSource<T, T1> : IDataSource<T, T1>
{
    public abstract List<T> GetAllRows();

    public abstract List<Object> GetValues(T1 row); 
}

And finally the less derived class:

public class XlsxDataSource : DataSource<Row, Row>, IDataSource<Row, Row>
{
    public abstract List<Row> GetAllRows();

    public abstract List<Object> GetValues(Row row); 
}

Here is the casting:

IDataSource<Object, Object> datasource = (IDataSource<Object, Object>) new XlsxDataSource();

But casting an XlsxDataSource to an IDataSource object is resulting in an InvalidCastException :

Unable to cast object of type 'EDS.Models.XlsxDataSource' to type 'EDS.Models.IDataSource`2[System.Object,System.Object]'.

You'll need a co-variant interface for this to work

public interface IDataSource<out T> {}

public abstract class DataSource<T> : IDataSource<T> {}

public class XlsxDataSource : DataSource<Row> {}

Will allow the following

XlsxDataSource dataSource = new XlsxDataSource();
var dataSources = new Dictionary<long, IDataSource<Object>>();
dataSources[dataSource.id] = (IDataSource<Object>) dataSource;

But note that you should only use co-variance if the type T is only used as the return types of methods or for read only properties (that is it only comes out of the interface and is never inserted in).

EDIT

Based on your edit you now have two generic types T and T1 . You have defined T1 as contra-variant with in which is correct for how you use that type, however contra-variance only allows you to assign to a more derived type and not to a less derived type. Further you didn't set T to be variant at all though it could be co-variant if you change the return type to IEnumerable<T> . Here's what you can do.

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 {}

That would allow you to do something like

XlsxDataSource dataSource = new XlsxDataSource();
IDataSource<object, DerivedRow> interfaceReference = dataSource;

You can assign it to allow for more derived types of T1 because you are passing them into the interface. So the method GetValues(Row row) can take a DerivedRow , but it cannot take a object . Then for T1 you can assign to less derived types becuase you are getting those values out of the interface. So the method GetAllRows returns an IEnumerable<Row> which is assignable to IEnumerable<object> because IEnumerable is covariant and object is less derived than Row .

Now what you could do is create a non-generic interface to handle this.

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();
    }
}

Which would allow you to do

XlsxDataSource dataSource = new XlsxDataSource();
var dataSources = new Dictionary<long, IDataSource>();
dataSources[5] = dataSource;

But now you've lost strong typing, which means you don't know the actual type of the objects in the lists that are returned by the interfaces methods and worse you could pass in an object to GetValues that will result in a casting exception. So ultimately a better question is why does the dictionary need to down grade the generic type on something that is clearly not co-variant.

That's error occurs because generic classes in C# aren't covariant.

Covariance enables you to use a more derived type than originally specified.

In C# covariance is possible in interfaces, so if you can convert DataSource<T> class to an interface a possible solution is:

public interface IDataSource<out T>
{
    ...
}

public abstract class DataSource<T> : IDataSource<T>
{
    ...
}

public class XlsxDataSource : DataSource<Row>
{
    // Overriden methods and constructor
}

And works with the dictionary like this:

XlsxDataSource dataSource = new XlsxDataSource();
var dataSources = new Dictionary<long, IDataSource<Object>>();
dataSources[dataSource.id] = (IDataSource<Object>)dataSource;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM