0

I have a small problem I can't seem to get working right. I currently have a list of IModelConverters like this:

public class ModelConverterList : List<IModelConverter<IDataCollection>>
{
}

And I am trying to add entries like this:

public static void AddModelConverter<T>(IModelConverter<T> converter) 
  where T: IDataCollection
{
    CheckForSetup();
    modelConverters.Add(converter);
}

CheckForSetup is a method checking if the list isn't null (along with some other unrelevant checks). This is the interface I want to cast to:

public interface IModelConverter<in T>: IConverter where T: IDataCollection
{
    ResponseData Activate(T documents, ServiceContext services, bool overrideIfNeeded = false);

    bool ContainsFile(T document, ServiceContext services);
}

However, it doesn't want to cast to this interface. I tried casting it to IModelConverter<T> and IModelConverter<IDataCollection>

The object I want to add has an abstract class that uses the interface, could this be why it isn't working?

I also thought about multiple references to it, but it doesn't seem like that's the case.

EDIT:

The error I get in the editor is this: "Argument 1: cannot convert from 'Extensions.Abstractions.Interfaces.IModelConverter' to 'Extensions.Abstractions.Interfaces.IModelConverter'"

And the class I want to add is this:

public class LanguageConverter : DocumentConverterCreatorBase<LanguageCollection>
{
protected override void ActivateDocument(LanguageCollection collection, ServiceContext services, bool overrideIfNeeded)
{
  ILocalizationService fs = services.LocalizationService;
  foreach (LanguageType language in collection.List)
  {
    if (fs.GetLanguageByIsoCode(language.CultureAlias) != null && overrideIfNeeded)
      fs.Delete(fs.GetLanguageByIsoCode(language.CultureAlias));

    if (fs.GetLanguageByIsoCode(language.CultureAlias) == null)  
      fs.Save(language.Construct(services));
  }
}

public override bool ContainsFile(LanguageCollection document, ServiceContext services)
{
  ILocalizationService ls = services.LocalizationService;
  foreach (LanguageType item in document.List)
  {
    if (ls.GetLanguageByIsoCode(item.CultureAlias) == null)
      return false;
  }
  return true;
}

}

And the abstract class is this:

public abstract class DocumentConverterCreatorBase<T>: IAssetConverter, IModelConverter<T>, IModelCreator where T : IDataCollection

The abstract class has two abstract methods for the methods in the interface.

The IDataCollection is nothing but a list of data. The interface is as follows:

public interface IDataCollection
  {
    int GetCount();
  }
patrick
  • 23
  • 6
  • 1
    What error do you have and how are you casting? Can you put the code? – Ignacio Soler Garcia Nov 06 '17 at 10:48
  • 1
    Can you please put enough code for us to copy, paste, and compile your code so that **the only error that we see** is the one you're getting? Right now I get so many errors. – Enigmativity Nov 06 '17 at 10:49
  • Well, it's not an error as it doesn't let me build it at all. Here is what I get: "Argument 1: cannot convert from 'Extensions.Abstractions.Interfaces.IModelConverter' to 'Extensions.Abstrations.Interfaces.IModelCOnverter'" – patrick Nov 06 '17 at 10:49
  • @patrick - Please edit your question with the code. – Enigmativity Nov 06 '17 at 10:50
  • 5
    Please post a [mcve]. – Lasse V. Karlsen Nov 06 '17 at 10:52
  • 2
    IModelConverter vs IModelCOnverter, track down the one with odd capitalization to begin with! – tolanj Nov 06 '17 at 11:00
  • 1
    A specific `IModelConverter` implementing class can work with a `T` or anything derived from it - but it's *not* guaranteed to be able to cope with *any* `IDataCollection` type. The type system is doing its job here of not letting you write un-type-safe code. – Damien_The_Unbeliever Nov 06 '17 at 11:01
  • So, basicly it's not guaranteed that T is the IDataCollection I want? But shouldn't the where clause catch that? – patrick Nov 06 '17 at 11:03
  • The type is parameterized on `T`. You're guaranteed that `T` is *some specific* `IDataCollection` type, and that the type can cope with objects of type `T` or something more derived. But you're not guaranteed that the type could cope with some *other* type which also implements `IDataCollection` but *isn't* in `T`'s hierarchy. – Damien_The_Unbeliever Nov 06 '17 at 11:09
  • Possible duplicate of [Cast interface to its concrete implementation object or vice versa?](https://stackoverflow.com/questions/539436/cast-interface-to-its-concrete-implementation-object-or-vice-versa) – Grimley Nov 06 '17 at 11:09
  • Have you put a typo in your question? `Extensions.Abstrations.Interfaces.IModelCOnverter` looks incorrect. – Matt Hogan-Jones Nov 06 '17 at 11:29
  • @Damien_The_Unbeliever Could types that implement IDataCollection but that aren't in the T hierarchy be classes? I can imagine the safety because of interfaces. I have a hard time understanding it. Could you provide a small example? (Might be because my primary language is not English) – patrick Nov 06 '17 at 12:27

1 Answers1

3

The problem you're seeing is one of co-variance.

If you have these type definitions:

public class ModelConverterList : List<IModelConverter<IDataCollection>> { }
public interface IModelConverter<in T> : IConverter where T : IDataCollection { }
public interface IDataCollection { }
public interface IConverter { }

...then with this code:

private static ModelConverterList modelConverters = new ModelConverterList();

public static void AddModelConverter<T>(IModelConverter<T> converter) where T : IDataCollection
{
    modelConverters.Add(converter);
}

...you get the following error:

CS1503 Argument 1: cannot convert from 'IModelConverter<T>' to 'IModelConverter<UserQuery.IDataCollection>'

Even though we know that T inherits from IDataCollection, it isn't the same as saying that IModelConverter<T> inherits from IModelConverter<IDataCollection> - it doesn't. So there is no cast from IModelConverter<T> to IModelConverter<IDataCollection>.

This compiles:

public class ModelConverterList : List<IModelConverter<IDataCollection>> { }
public interface IModelConverter<out T> : IConverter where T : class, IDataCollection { }
public interface IDataCollection { }
public interface IConverter { }

private static ModelConverterList modelConverters = new ModelConverterList();

public static void AddModelConverter<T>(IModelConverter<T> converter) where T : class, IDataCollection
{
    modelConverters.Add(converter);
}

But I've changed the definition of IModelConverter from in T to out T and added a class constraint.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Great solution. Though, I am not able to use the out T, because of the methods I have in the interface. Would that change something in the code? – patrick Nov 06 '17 at 12:09
  • @patrick - Yes, it allows my sample code to compile, but it would break your interface. You basically can't do what you want. – Enigmativity Nov 06 '17 at 12:26
  • Ah, then I will see if I can find a solution that involves the out T, or another solution. Thanks for the help though. – patrick Nov 06 '17 at 12:29
  • @patrick - A common approach is to have `IModelConverter` inherit from a non-generic `IModelConverter` which has a `ModelType` property for getting the derived interface's generic type. – Enigmativity Nov 06 '17 at 12:57
  • So if I have an IModelConverter interface, it would only have an IDataCollection property? Or would it also have the two methods in it from the IModelConverter interface? I did try this before going here. But if I make a non-generic interface, I also have to put the two methods in there, meaning that if I implement the generic interface, I have to implement 4 methods in total. Is that a normal tradeoff or is there a better way to handle that? – patrick Nov 06 '17 at 13:03
  • I did get it working the way I want right now. Though I do have the 4 methods in place right now. Any idea if there is a better way to do that? I currently have the following structure: IModelConverter -> IModelConverter -> DocumentConverterCreatorBase. The DocumentConverterCreatorBase has the 4 methods because it isn't able to convert IDataCollection to T. – patrick Nov 06 '17 at 13:14