2

I have the following situation which is confusing me.

Given:

public class ThermoRawFile : MsDataFile { }

public abstract class MSDataFile { }

I am unable to run this LINQ IEnumerable.ToDictionary() method:

IEnumerable<string> rawFiles;
Dictionary<string, MSDataFile> dataFiles = rawFiles.ToDictionary(
    file => file,
    file => new ThermoRawFile(file)
);

as the compiler gives the following error:

"Cannot implicitly convert type System.Collections.Generic.Dictionary<string,CSMSL.IO.Thermo.ThermoRawFile> to System.Collections.Generic.Dictionary<string,CSMSL.IO.MSDataFile>"

Why cannot it implicit convert ThermoRawFile into MSDataFile when it is a simple inheritance?

This works just fine:

 MSDataFile dataFile = new ThermoRawFile("someFile.raw");
Moop
  • 3,414
  • 2
  • 23
  • 37

3 Answers3

3

The error does not say that it cannot convert CSMSL.IO.Thermo.ThermoRawFile to CSMSL.IO.MSDataFile, only that it cannot convert a generic dictionary type based on CSMSL.IO.Thermo.ThermoRawFile to a generic dictionary type based on CSMSL.IO.MSDataFile. This is expected, because these types are not covariant.

You can fix this by supplying a cast:

IEnumerable<string> rawFiles;
Dictionary<string, MSDataFile> dataFiles = rawFiles.ToDictionary(
    file => file,
    file => (MSDataFile)new ThermoRawFile(file)
);
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
3

The problem is that type inference has inferred your ToDictionary call to be:

rawFiles.ToDictionary<string, string, ThermoRawFile>(...)

That means the returned value will be of type Dictionary<string, ThermoRawFile>, and there's no conversion from that to Dictionary<string, MSDataFile> (partly because dictionaries are invariant in general, and partly because all classes are invariant).

Now you can cast the value selection part to make type inference work:

var dataFiles = rawFiles.ToDictionary(
    file => file,
    file => (MSDataFile) new ThermoRawFile());

Or you could just explicitly specify the type arguments:

var dataFiles = rawFiles.ToDictionary<string, string, MSDataFile>(
    file => file,
    file => new ThermoRawFile());

That will still be fine, as the compiler can convert the lambda expression file => new ThermoRawFile() into a Func<string, MSDataFile>.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
0

This has to do with covariance. A ThermoRawFile is a MSDataFile, but a Dictionary<string, ThermoRawFile> is not a Dictionary<string, MSDataFile>.

If it were, then you could do something like this:

public class FooFile : MsDataFile { }

dataFiles["blah"] = new FooFile();

This would be putting a FooFile into a Dictionary<string, ThermoRawFile>, which would wreak all kinds of havoc.

In this case you need to construct a dictionary that is actually of type Dictionary<string, MSDataFile> to begin with, even if you populate it with entirely ThermoRawFile values. You can do so by adding in a cast (as shown in a bunch of other answers here):

IEnumerable<string> rawFiles;
Dictionary<string, MSDataFile> dataFiles = rawFiles.ToDictionary(
    file => file,
    file => (MSDataFile)new ThermoRawFile(file)
);
Servy
  • 202,030
  • 26
  • 332
  • 449