0

So, I have a project with 5 base classes:

File
FileRecord
FileDetailRecord : FileRecord
FileHeaderRecord : FileRecord
RecordDetail

Those relate together like:

A File contains FileRecords, a FileRecord can either be a FileDetailRecord or FileHeaderRecord, and FileRecords contain RecordDetails

I then have two subsets of those base classes, Output and Input. So I have

OutputFile : File
OutputFileDetailRecord : FileDetailRecord
OutputFileHeaderRecord : FileHeaderRecord
OutputRecordDetail : RecordDetail

and the same thing for Input. The issue I'm currently having is with one of the members in my File classes (File, OutputFile, InputFile).

My file class is written like so:

public class File {

    public virtual FileHeaderRecord FileHeaderRecord { get { return fileHeaderRecord; } }
    protected virtual FileHeaderRecord fileHeaderRecord { get; set; }

    public virtual SortedList<int, FileDetailRecord> FileDetailRecords { get { return fileDetailRecords; } }
    protected virtual SortedList<int, FileDetailRecord> fileDetailRecords { get; set; }

}

What I'm trying to do in my OutputFile class is:

public class OutputFile : File {

    public override FileHeaderRecord FileHeaderRecord {
        get {
            if (fileHeaderRecord == null)
                fileHeaderRecord = new OutputFileHeaderRecord();

            return fileHeaderRecord;
        }
    }       

    public override SortedList<int, FileDetailRecord> FileDetailRecords {
        get {
            if (fileDetailRecords == null)
                fileDetailRecords = (SortedList<int, OutputFileDetailRecord>)new SortedList<int, FileDetailRecord>();
        }
    }

}

With my OutputFileDetailRecord class:

public class OutputFileDetailRecord : FileDetailRecord {

    public OutputFileDetailRecord()
        : base() {

    }

    public override bool IsValid {
        get {
            return base.IsValid;
        }
    }

}

The error I'm getting is

Error   1   Cannot convert type     'System.Collections.Generic.SortedList<int,MerchBulkLoad.Utils.Base.FileDetailRecord>' to     'System.Collections.Generic.SortedList<int,MerchBulkLoad.Utils.Output.OutputFileDetailRecord>'

I also tried to just implictly cast it like this:

fileDetailRecords = new SortedList<int, OutputFileDetailRecord>();

But that just told me I couldn't implicitly cast it. It's been several months since I worked with C# at all so I'm extremely rusty at the moment. Can anyone point out what I'm doing wrong here?

Ben Black
  • 3,751
  • 2
  • 25
  • 43
  • 5
    See [Covariance and contravariance](http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)) - by default C# generics are invariant. Also see [Creating Variant Generic Interfaces](http://msdn.microsoft.com/en-us/library/dd997386.aspx) (YMMV). – user2864740 Jan 17 '14 at 22:48
  • 1
    To help explain why Covaranace is a problem, what if `SortedList` contained a `InputFileDetailRecord`? (I know you don't have one, but what if I took your code and added that class at a later date) – Scott Chamberlain Jan 17 '14 at 22:50
  • Okay, that makes sense Scott. – Ben Black Jan 17 '14 at 22:51
  • This is solvable but you will need to create some interfaces. I don't have the time until a few hours from now to write up a full answer but hopefully someone can explain in detail sooner. For now, take a look at http://stackoverflow.com/questions/3445631/still-confused-about-covariance-and-contravariance-in-out, look at Nat's answer. – Scott Chamberlain Jan 17 '14 at 22:52
  • 2
    The solution here is probably to use `IEnumerable` instead of `SortedList`. If you want to define a `SortedList` property in `File`, you'll likely need to make your it generic, with the type of the element as a parameter. e.g. `OutputFile : File` – Kendall Frey Jan 17 '14 at 22:55
  • That answer was extremely helpful Scott, thanks for the pointer on that! – Ben Black Jan 17 '14 at 23:05

1 Answers1

1

As the comments told, generic types are invariant; and you are encountering the variance problem with your class design. Define your own sorted list contract, life can be easy.

Say we declare the interface IMySortedList and we can use it instead of SortedList to declare the properties in your classes :

public interface IMySortedList<TKey, out TValue>: IEnumerable {
    // .. 
}

and a class implements it:

public class MySortedList<TKey, TValue>
    : SortedList<TKey, TValue>, IMySortedList<TKey, TValue> {
    // .. 
}

So that we can say:

public class OutputFile: File {
    public override FileHeaderRecord FileHeaderRecord {
        get {
            if(fileHeaderRecord==null) {
                fileHeaderRecord=new OutputFileHeaderRecord();
            }

            return fileHeaderRecord;
        }
    }

    public override IMySortedList<int, FileDetailRecord> FileDetailRecords {
        get {
            if(fileDetailRecords==null) {
                fileDetailRecords=
                    new MySortedList<int, OutputFileDetailRecord>();
            }

            return fileDetailRecords;
        }
    }
}

Note the code above lacked the implementation detail, it should be of your design, not of mine ..

Ken Kin
  • 4,503
  • 3
  • 38
  • 76