Search code examples
c#.netgenericscovariance

List with Multiple Generic Objects (Covariance Issue)


I've been trying to get this solved, and have looked at the similar issues, but am not getting the proper solution here.

I am using a Generic class to hold configuration for an export

public class SpreadsheetConfiguration<T> where T : class 
{
    public IEnumerable<T> ExportData {get; set;}
    // More stuff here
}

I have a situation where I need a list of these, which might not be of the same type, for example like this

public byte[] ExportMultipleSheets(IEnumerable<SpreadsheetConfiguration<object>> toExport)

But I cannot for the life of me, figure out how to make this work and I've looked at the other routes above with regards to making an ISpreadsehetConfiguration or otherwise.

This is for an OpenSource project here: https://github.com/IowaComputerGurus/netcore.utilities.spreadsheet

I know I'm missing something, but I've tried all of the above, and still not get to a final usage where I can still do

var toExport = new SpreadsheetConfiguration<MyOtherClass>();

As it fails to convert


Solution

  • If your class is supposed to have a setter on that IEnumerable<T> then it cannot be covariant. Covariance is read-only, contravariance is write-only. If you need both and also need a collection of such configurations, then your design is flawed.

    If you are willing to have only get access on that property, then you first need to make an interface for your class, since variance works on generic interfaces only:

    public interface ISpreadsheetConfiguration<out T> where T : class
    {
        IEnumerable<T> ExportData { get; }
    }
    
    public class SpreadsheetConfiguration<T> : ISpreadsheetConfiguration<T> where T : class
    {
        public IEnumerable<T> ExportData {get; set;}
    }
    

    Notice the out keyword in the interface's type parameter declaration - it means that ISpreadsheetConfiguration<T> is covariant in T.

    Now you can do this:

    public byte[] ExportMultipleSheets(IEnumerable<ISpreadsheetConfiguration<object>> toExport);
    
    
    var toExport = new ISpreadsheetConfiguration<object>[]
    {
        new SpreadsheetConfiguration<MyOtherClass>(),
        new SpreadsheetConfiguration<CompletelyDifferentClass>()
    };
    
    ExportMultipleSheets(toExport);
    

    More on variance and why covariance cannot work with a type that allows both reads and writes with type T here.