4

I have following code below. I have two main interfaces IWatch and IWatchService. Oryginally Watch() was in IWatchService and there was no IWatch but since that CollectionService cannot use Watch() method i decided (ISP) to create IWatch interface additionally.In CollectionService i want in ctor pass either DatabaseWatchService or RemoteFilesWatchService therefore i put parameter type in ctor as IWatchService<IEntity> watchService nevertheless when in DoIt() method initialize fileWatcherServiceCsv variable it says:

Cannot implicitly convert type 'RemoteFilesWatchService' to 'IWatchService'. An explicit conversion exists (are you missing a cast?)

public interface IWatch
{
     void Watch();
}

public interface IWatchService<TDataEntity> where TDataEntity : IEntity
{
     INotificationFactory NotificationFactory { get; }
     ObservableCollection<TDataEntity> MatchingEntries { get; set; }
}

public interface IDatabaseWatchService<TDataEntity> : IWatchService<TDataEntity> where TDataEntity : IDatabaseEntity
{
     IDatabaseRepository<IDbManager> DatabaseRepository { get; }
}

public interface IRemoteFilesWatchService<TDataEntity> : IWatchService<TDataEntity> where TDataEntity : IFileEntity
{
     List<string> ExistingRemoteFiles { get; set; }
     List<RemoteLocation> RemoteLocations { get; set; }      
     IWinScpOperations RemoteManager { get; set; }
     IRemoteFilesRepository<IDbManager, TDataEntity> RemoteFilesRepository { get; }
}

public class RemoteFilesWatchService : IRemoteFilesWatchService<IFileEntity>, IWatch
{
     public INotificationFactory NotificationFactory { get; }
     public ObservableCollection<IFileEntity> MatchingEntries { get; set; }
     public List<string> ExistingRemoteFiles { get; set; }
     public List<RemoteLocation> RemoteLocations { get; set; }
     public IWinScpOperations RemoteManager { get; set; }
     public IRemoteFilesRepository<IDbManager, IFileEntity> RemoteFilesRepository { get; }

    public RemoteFilesWatchService(IWinScpOperations remoteOperator,
                IRemoteFilesRepository<IDbManager, IFileEntity> remoteFilesRepository,
                INotificationFactory notificationFactory)
    {
           RemoteManager = remoteOperator;
           RemoteFilesRepository = remoteFilesRepository;  //csv, xml or other repo could be injected
           NotificationFactory = notificationFactory;
    }

    public void Watch()
    {
    }
}

public class DatabaseWatchService : IDatabaseWatchService<DatabaseQuery>, IWatch
{
      public INotificationFactory NotificationFactory { get; }
      public ObservableCollection<DatabaseQuery> MatchingEntries { get; set; }
      public IDatabaseRepository<IDbManager> DatabaseRepository { get; }

      public DatabaseWatchService(IDatabaseRepository<IDbManager> databaseRepository,
            INotificationFactory notificationFactory)
      {
            DatabaseRepository = databaseRepository;
            NotificationFactory = notificationFactory;
      }

      public void Watch()
      {
      }
}

public class CollectionService
{
       private IWatchService<IEntity> _watchService;     

       public CollectionService(IWatchService<IEntity> watchService)
       {
             _watchService = watchService;
       }
}

class Run
{
       void DoIt()
       {          
            IWatchService<IEntity> fileWatcherServiceCsv = new RemoteFilesWatchService(new WinScpOperations(),
                                                                  new RemoteCsvFilesRepository(new DbManager(ConnectionDbType.MySql)),
                                                                  new NotificationFactory());

        var coll1 = new CollectionService(fileWatcherServiceCsv);
        }
}

public interface IEntity
{
}


public interface IFileEntity : IEntity
{
    int Id { get; set; }
    string Name { get; set; }
    bool IsActive { get; set; }
    bool RemoveFromSource { get; set; }
    string DestinationFolder { get; set; }
    RemoteLocation RemoteLocation { get; set; }
}

public interface IDatabaseEntity : IEntity
{
}

public class CsvFile : IFileEntity
{
    public int ColumnHeader { get; set; }
    public int ColumnsCount { get; set; }
    public string Separator { get; set; }
    public int ValuesRowStartposition { get; set; }
    public int ColumnRowPosition { get; set; }
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public bool RemoveFromSource { get; set; }
    public string DestinationFolder { get; set; }
    public RemoteLocation RemoteLocation { get; set; }
}

public class XmlFile : IFileEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public bool RemoveFromSource { get; set; }
    public string DestinationFolder { get; set; }
    public RemoteLocation RemoteLocation { get; set; }
    public string SubNode { get; set; }
    public string MainNode { get; set; }
}
5
  • 1
    You haven't provided all the code. Please provide an minimal reproducible example that demonstrates your problem.
    – rory.ap
    Commented Mar 26, 2019 at 12:35
  • It still doesn't compile even with your edit. It's missing a lot of definitions: IEntity, IDatabaseEntity, IFileEntity, and so on.
    – rory.ap
    Commented Mar 26, 2019 at 12:42
  • 1
    ok i put almost all code. Hope now it's enough to check the issue. To me it should work. Hmm
    – Henry
    Commented Mar 26, 2019 at 12:47
  • Well, you've done a good job editing your question. I haven't tested the code again but it looks like it's all there, or nearly. I would suggest you do follow the advice given here before asking any more questions: read resources in the help center and specifically How to Ask.
    – rory.ap
    Commented Mar 26, 2019 at 12:49
  • The quick and dirty guide to creating an MVC: Step 1. Paste your code into an empty visual studio project. Did it compile (or generate the error you are asking about)? If not, add more code. If forced to add a reference to third-party libraries, tag your question with those libraries or, if the library itself is irrelevant, either remove them or replace them with stubs. If your own libraries are required, inline them. Step 2. Remove any irrelevant code. If a class is necessary but irrelevant, replace it with a stub class. Yes, this process takes an hour. Please do it anyways.
    – Brian
    Commented Mar 26, 2019 at 13:16

3 Answers 3

9

This question gets posted almost every day. One more time!

A box of apples is not a box of fruit. Why not?

You can put a banana into a box of fruit, but you cannot put a banana into a box of apples, so a box of apples is not a box of fruit, because the operations you can perform on them are different. Similarly, a box of fruit is not a box of apples.

You're trying to use a IWatchService (box) of IFileEntity (apples) as an IWatchService of IEntity (fruit), and that's not legal.

Now, you might notice that in C# you can use an IEnumerable<Apple> where an IEnumerable<Fruit> is expected. That works just fine because there is no way to put a banana into an IEnumerable<Fruit>. In every member of IEnumerable<T> and IEnumerator<T>, the T comes out, not in.

If you are in that situation then you can mark your interface as

interface IWatchService<out T> ... 

And the compiler will verify that every T in the interface is used in "out" positions, and then will allow the conversion you want.

That conversion is called a generic covariant conversion and it only works when:

  • The generic type is an interface or delegate
  • The type parameter is marked out, and the compiler verifies that is safe
  • The varying types (Fruit and Apple, say) are both reference types. You can't do covariant conversions involving int and object, for example.
8
  • good post but i still do not get once i mark as IWatchService<out TDataEntity> - maybe stupid question but why then ObservableCollection<TDataEntity> MatchingEntries { get; set; } error Invalid variance: The type parameter 'TDataEntity' must be invariantly valid on 'IWatchService<TDataEntity>.MatchingEntries'. 'TDataEntity' . What should i do with this? Many thanks @!
    – Henry
    Commented Mar 26, 2019 at 13:10
  • @Henry: A box of apples is not a box of fruit. An observable collection is not safe for covariance because a T can go both in and out of an observable collection. If you change it to a type where T only goes out, like IEnumerable<T>, then it will be covariantly valid. Commented Mar 26, 2019 at 13:16
  • Hmm now i see i need to put only { get; } for my property. The problem is later on e.g in DatabaseWatchService or RemoteFileWatchService class when i want to use MatchingEntires e.g Count(), Add() or whatever it is not available anymore. How to solve that? Thanks so far for your contribution here.
    – Henry
    Commented Mar 26, 2019 at 13:42
  • So by "every T in the interface is used in "out" positions" means the members of the interface that use T are either methods that return T or take T as an argument marked with out, or are read-only properties or fields? What about a constructor taking in a T?
    – rory.ap
    Commented Mar 26, 2019 at 16:20
  • @rory.ap: No. Returning a T is OK. out parameters unfortunately are considered inputs for two reasons (1) because you can read from the out parameter, and (2) because from the runtime's perspective, out and ref are the same thing, and a ref is an input. Since only interfaces and delegates can be variant, there are no fields. Properties of interfaces must be get-only. Delegates and interfaces do not have constructors. Commented Mar 26, 2019 at 16:46
5

Your RemoteFilesWatchService implements interface IWatchService<IFileEntity>, while your CollectionService expects a IWatchService<IEntity>. The two types are different, that's why it cannot convert. Modify your CollectionService to accept IWatchService<IFileEntity> instead, or make RemoteFilesWatchService implement IRemoteFilesWatchService<IEntity>. Or use a non-generic interface in CollectionService instead.

You cannot have a IWatchService<IFileEntity> and treat it as a IWatchService<IEntity>. Compare it to a List<T> for example. You cannot expect to be able to do this:

class Animal {}
class Bird : Animal {}
class Elephant : Animal {}

var birds = new List<Bird>();

// compiler does not allow this...
List<Animal> animals = birds;

// ...because there is no point in adding elephants to a list of birds.
animals.Add(new Elephant());
11
  • In Collection Service i want to be able to use either RemoteFilesWatchService or DatabaseWatchService - i thought the one what is common is that - ok they implementing diffrent interfaces but those interfaces inherits from IWatchService therefore i put this interface as the one in parameter. Hope you got my point
    – Henry
    Commented Mar 26, 2019 at 12:54
  • @Henry See my extra explanation as to why you cannot do that. Commented Mar 26, 2019 at 13:16
  • so it means i cannot put list of birds into list of animals but i could add one bird into animals'list
    – Henry
    Commented Mar 26, 2019 at 13:29
  • 1
    but funny thing is if i add two birds into animals list i also should be able to put all birds into animals list means full it up or should i read like it means that List<Animal> animals = birds; is like replacing Type itself ?+
    – Henry
    Commented Mar 26, 2019 at 13:39
  • 1
    In this case List<Animal> animals = birds is an implicit cast. So we are casting the List<Bird> to a List<Animal> (impossible). Under water it would still be the same List<Bird>, which is why you cannot add Elephant to it, but since we're treating it as a List<Animal> it seems that it would be possible to do so. Hope that answers your question, otherwise, we should open a chat. Commented Mar 26, 2019 at 13:44
0

Making a slight change to take support from variance, should fix your issue as follows:

    public interface IEntity
    {

    }

    public interface IFileEntity : IEntity
    {
        ...
    }
    public interface IWatchService<out TDataEntity> where TDataEntity : IEntity //note the "out" keyword here.
    {
    }

You can learn more about Variance in Generic Interfaces Here

1
  • Oh!! Come on... @Eric is already here with a nice explanation!
    – Siva Gopal
    Commented Mar 26, 2019 at 12:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.