0

I'm experiencing an unexpected issue.

I would like to cast a child class with a special type to its parent class with a generic type but the compiler does 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'

And I don't get why, as XlsxDataSource is a child from DataSource and the type in XlsxDataSource is explicitly Row which 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]'.
6
  • 2
    That's because it isn't a DataSource<Object>, it's a DataSource<Row>. If you want co-variance you'll have to work with interfaces instead of classes. Commented Mar 2, 2016 at 16:04
  • But I have a generic constructor in my DataSource class so there is no redundant code, wich is impossible with an interface. Commented Mar 2, 2016 at 16:05
  • 1
    You can still have an abstract class between the interface and the child classes. It's just that co-variance only works if you use an interface reference. msdn.microsoft.com/en-us/library/dd997386.aspx Commented Mar 2, 2016 at 16:08
  • why do you need to cast at all? Commented Mar 2, 2016 at 16:12
  • Because otherwise I can not add an XlsxDataSource object to a Dictionary that only accept DataSource<Object> objects... When I try, here is the error that I get: Cannot implicitly convert type 'EDS.Models.XlsxDataSource' to 'EDS.Models.DataSource<object>' Commented Mar 2, 2016 at 16:14

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the snippet, I was looking on how to implement co-variant interfaces :)
I tried your solution but it didn't work, could you check my edit please?
@Renlidacha I've updated my answer based on your edit. But basically what you tried didn't work because it was not co-variant on both generic types.
Thanks a lot. The point is that I have to manage several types of DataSource such as Excel files, Acces files, etc. . As the methods names are the same for each datasource but not the implementation, I need to be able to cast specific datasources such as XslxDataSource to a generic datasource as I don't know what will be the type of the datasource at runtime.
1

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;

1 Comment

That's contra-variant. You need to use out for co-variance.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.