0

I have a FileRecord class with the following definition:

public class FileRecord
{
    public IList<IDataRecord> DataRecords { get; set; }
    public IList<ImageFile> Images { get; set; }
    public string IndexFileName { get; set; }
    public string IndexFilePath { get; set; }
}

please note that datarecords is a IList of an interface type.

I then have a method that creates a file record for each type of datarecords we have:

fileRecord = new FileRecord
{
    DataRecords = CsvFile.Read<VallDataData>(Path.GetFullPath(indexFile.FullName)).Where(x => x.GiftAidValidity == "Y").ToList<IDataRecord>()
};

if I remove the implicit casting on the ToList<IDataRecord>() to ToList() I get this error message:

Error   CS0266  Cannot implicitly convert type 'System.Collections.Generic.List<VallDataData>' to 'System.Collections.Generic.IList<IDataRecord>'. An explicit conversion exists (are you missing a cast?)  

even though VallDataData implements IDataRecord

why is this?

Thanks.

franklores
  • 375
  • 3
  • 14
  • 2
    Instead of `.ToList()` try this: `.Cast().ToList();` – Szer Mar 17 '16 at 15:14
  • '.ToList()' works, I'm casting back to the interface, I just want to know why I can't do just '.ToList()' if my VallDataData type implements the interface. – franklores Mar 17 '16 at 15:21
  • Because although the variable is a `IList` the actual type is still a `List`, Because `IList` has a `Add(T)` method I could do `fileRecord.Add(new Bar())` (Assuming `Bar` also implemented `IDataRecord`) this would cause a `Bar` to be added to a `List` which is illegal. – Scott Chamberlain Mar 17 '16 at 15:23
  • @franklores see my answer, IList is not contravariant – Alex Sikilinda Mar 17 '16 at 15:32
  • Thank you for all the answers I learnt something new today. I settled for @Rawling suggestion of using a IReadOnlyList – franklores Mar 17 '16 at 15:48

3 Answers3

4

An IList<IDataRecord> should let you add any IDataRecord to it; your list only lets you add VallDataData.

If you only need to read from the list, try using IReadOnlyList<IDataRecord> instead, as it is actually covariant.

Rawling
  • 49,248
  • 7
  • 89
  • 127
  • 1
    Also, if you need indexer access (`fileRecord.DataRecords[0]` for example) you can also use the newer `IReadOnlyList` – Scott Chamberlain Mar 17 '16 at 15:20
  • @ScottChamberlain Oh, awesome. I need to start using those. – Rawling Mar 17 '16 at 15:24
  • Yea, `IReadonlyList` and `IReadOnlyDictionary` where added in 4.5, both very useful (Too bad the dictionary version is not co-variant, but it makes sense sence it needs to be able to be a `IEnumerable>` and `KeyValuePair` can't be co-variant do to it not being a interface) – Scott Chamberlain Mar 17 '16 at 15:26
2

IList<T> is neither contravariant or covariant, because it has T both on input and output positions in its methods. That is why you get:

Error CS0266 Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IList'. An explicit conversion exists (are you missing a cast?)

You should use either Cast or ToList<T> here.

If IList<T> were covariant or contravariant it should have either in or out before it's generic parameter T (see IEnumerable<T> for example), but it is not the case.

Alex Sikilinda
  • 2,928
  • 18
  • 34
  • This is a valid way of doing it too, if the user does actually want a `IList` he can add and remove any type of `IDataRecord` from. – Rawling Mar 17 '16 at 15:19
0

You cannot implicitly convert List<Derived> to IList<Base> because IList<T> is not Covariant (see about covariance).

IList<T> is not covariant because it is mutable (see this SO question & answer)

Consider the following if it was covariant:

public interface INameable 
{
    string Name { get; }
}

public class Person : INameable
{
    public Person(string name)
    {
        Name = name;
    }

    public string Name { get; private set; }
}

public class Star : INameable
{
    public Star(string name, int brightness)
    {
         Name = name;
         Brightness = brightness;
    }

    public int Brightness { get; private set; }

    public string Name { get; private set; }

}

public void MyMethod() 
{
    List<Person> people = new List<Person> { new Person("Sam"), new Person("Pop") };

    // note: I *cannot* do the following, but let's pretend I can
    IList<INameable> nameables = people;

    // note: nameables and people technically reference the same object
    // so consider what happens when I do this:
    nameables.Add(new Star("Alpha Centauri", 9000));

    // nameables is cool, but oh man, my people list is now totally messed up. Sabotage!
    // Thanks goodness for covariance in generics!
}

In summary, I would consider using IEnumerable<T> in place of IList<T> when you can. It's typically good practice to not let your objects be mutable (unless you really want them to be).

Example of immutability (from OP's code):

public class FileRecord
{
    public IEnumerable<IDataRecord> DataRecords { get; private set; }
    public IEnumeable<ImageFile> Images { get; private set; }
    public string IndexFileName { get; private set; }
    public string IndexFilePath { get; private set; }
}
Community
  • 1
  • 1
souldzin
  • 1,428
  • 11
  • 23
  • I had them initially as IEnumerable, but I need to repeatedly access the Count property which doesn't exist in IEnumerable., I have settled for using IReadOnlyCollection which more clearly represents the access I'll have on this list anyway and as I now know... Is covariant. Thanks. – franklores Mar 17 '16 at 15:45
  • @franklores: Ok. Just so you're informed, `IEnumerable` does have a `Count()` in LINQ, which is optimized if the backing implementation implements `ICollection` (see this SO answer: http://stackoverflow.com/a/853478/1708147). Either way, you're good so don't feel like you *have* to use `IEnumerable`. – souldzin Mar 17 '16 at 15:50