Search code examples
c#c#-4.0covariance

covariance error when returning an element of List<DerivedClass>


I'm dealing with a problem which is giving me a headhache. I've been reading a few posts about the issues related with covariance and contravariance in C# but anything seems to solve this one.

public abstract class BaseImporter<TEntity> : IImporter<TEntity> where TEntity : class
{
    //don't pay attention to this method
    public TEntity SomeMethodWhichUsesTheGenericType();

    //this is the important one
    protected abstract IBaseReader<IFileData> GetReader(Stream file, params object[] args);
}

public interface IImporter<out T> where T : class
{
    //don't pay attention to this method
    T SomeMethodWhichUsesTheGenericType();
}

public interface IBaseReader<out T> where T : IFileData
{
   T GetFileData(Stream stream);

    bool CanHandleStream(Stream stream, params object[] args);
}

public interface IFileData
{

}

Look that I have created a BaseImporter abstract class which exposes a method GetReader() which returns a IBaseReader to a certain IFileData. If you look the declaration of IBaseReader it specifies the "out" keyword related with covariance concept.

In other assembly, I have declared some classes which make use of these ones.

public class JETFileData : IFileData
{
    List<string> Data { get; set; }
}

public class ExcelFileReader : BaseReader<JETFileData>
{
    public override bool CanHandleStream(Stream stream, params object[] args)    
    {
        return true;
    }

    public override JETFileData GetFileData(Stream stream)
    {
        return new JetFileData() { Data = new List<string>() };
    }
}

Look that JETFileData is a IFileData and the ExcelFileReader is a reader which passing a Stream returns a JETFileData

And then I have a concrete Importer derived from BaseImporter. e.g.:

public class US_ETDocumentImporter : BaseImporter<US_ETDocument>
{
    public List<IBaseReader<JETFileData>> Readers { get; set; }

    public US_ETDocumentImporter()
    {
        this.Readers = new List<IBaseReader<JETFileData>>()
        {
            new ExcelFileReader()
        };   
    }

    //HERE IS WHERE I GOT THE ERROR!!!
    protected override IBaseReader<JETFileData> GetReader(Stream file, params object[] args)
    {
        //decide which reader is able to be used to process this file
        return this.Readers.Single(r => r.CanHandleStream(file, args));
    }
}

So, the error says:

'Importers.US_ETDocumentImporter.GetReader(System.IO.Stream, params object[])': return type must be 'Core.IBaseReader<Core.IFileData>' to match overridden member 'Core.BaseImporter<Documents.US_ETDocument>.GetReader(System.IO.Stream, params object[])' 

And an image could be helpful too: enter image description here

As I said, I believe that the error is related with covariance, but I am not completely sure. It seems that the compiler doesn't get that JETFileData is an IFileData I guess. What am I doing wrong?


Solution

  • I don't think this has anything to do with covariance, instead the problem is you're trying to change a return type in an override method.

    Change BaseImporter to have another generic type, and constrain it to IFileData

    public abstract class BaseImporter<TEntity, TFileData> : IImporter<TEntity> 
          where TEntity : class 
          where TFileData: IFileData
    {
            //don't pay attention to this method
            public TEntity SomeMethodWhichUsesTEntity()
            {
                return null;
            }
    
            // changed IFileData to TFileData
            protected abstract IBaseReader<TFileData> GetReader(Stream file, params object[] args);
    }
    

    Then, when you override this in US_ETDocumentImporter you get exectly what you want:

    public class US_ETDocumentImporter : BaseImporter<US_ETDocument,JETFileData>
    {
        public List<IBaseReader<JETFileData>> Readers { get; set; }
    
        public US_ETDocumentImporter()
        {
            this.Readers = new List<IBaseReader<JETFileData>>()
            {
                new ExcelFileReader()
            };   
        }
    
        //HERE IS WHERE I GOT THE ERROR!!!
        protected override IBaseReader<JETFileData> GetReader(Stream file, params object[] args)
        {
            //decide which reader is able to be used to process this file
            return this.Readers.Single(r => r.CanHandleStream(file, args));
        }
    }