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; }
}
Henry
  • 537
  • 1
  • 9
  • 22
  • 1
    You haven't provided all the code. Please provide an [mcve] that demonstrates your problem. – rory.ap Mar 26 '19 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 Mar 26 '19 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 Mar 26 '19 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] and specifically [ask]. – rory.ap Mar 26 '19 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 Mar 26 '19 at 13:16

3 Answers3

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.
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • good post but i still do not get once i mark as IWatchService - maybe stupid question but why then ObservableCollection MatchingEntries { get; set; } error Invalid variance: The type parameter 'TDataEntity' must be invariantly valid on 'IWatchService.MatchingEntries'. 'TDataEntity' . What should i do with this? Many thanks @! – Henry Mar 26 '19 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`, then it will be covariantly valid. – Eric Lippert Mar 26 '19 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 Mar 26 '19 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 Mar 26 '19 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. – Eric Lippert Mar 26 '19 at 16:46
  • @rory.ap: That is not a strict definition of the rules. If you want the exact rules, read the C# specification, and also my article on the subject: https://blogs.msdn.microsoft.com/ericlippert/2009/12/03/exact-rules-for-variance-validity/ – Eric Lippert Mar 26 '19 at 16:47
  • @rory.ap: For a detailed explanation of why `out T` parameters are not legal in an `out T` variant interface, see https://stackoverflow.com/questions/2876315/ref-and-out-parameters-in-c-sharp-and-cannot-be-marked-as-variant/2877515#2877515 – Eric Lippert Mar 26 '19 at 16:50
  • @EricLippert can you answer on my latest comment? If i put out to like: IWatchService then ObservableCollection MatchingEntries { get; } will be having only get and that means later on i will be not able to e.g Add() on this list...How to overcome this to be able to use Add for instance? – Henry Mar 27 '19 at 11:11
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());
Jesse de Wit
  • 3,867
  • 1
  • 20
  • 41
  • 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 Mar 26 '19 at 12:54
  • @Henry See my extra explanation as to why you cannot do that. – Jesse de Wit Mar 26 '19 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 Mar 26 '19 at 13:29
  • Yes, If you'd have a `List` you'd be able to add either `Bird` of `Elephant`, because they are both animals. Makes sense this way doesn't it? – Jesse de Wit Mar 26 '19 at 13:30
  • so it's more like safety thing to not replace as one big picture object because it could completly vary but can change parts of it. – Henry Mar 26 '19 at 13:34
  • Perhaps you could put it like that. And you don't actually need the generic types inside `CollectionService` anyway, because you don't care about the types at that point. – Jesse de Wit Mar 26 '19 at 13:37
  • 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 animals = birds; is like replacing Type itself ?+ – Henry Mar 26 '19 at 13:39
  • 1
    In this case `List animals = birds` is an implicit cast. So we are casting the `List` to a `List` (impossible). Under water it would still be the same `List`, which is why you cannot add `Elephant` to it, but since we're treating it as a `List` it seems that it would be possible to do so. Hope that answers your question, otherwise, we should open a chat. – Jesse de Wit Mar 26 '19 at 13:44
  • not possible to chat for me (reputatin or whatever it's called), can you open chat i could join to discuss couple things (if possible of course) ? – Henry Mar 26 '19 at 13:50
  • I understand that example, but going back to my main topic If i put out to like: IWatchService then ObservableCollection MatchingEntries { get; } will be having only get and that means later on i will be not able to e.g Add() on this list... How to overcome this to be able to use Add for instance? – Henry Mar 27 '19 at 11:12
  • I'd recommend you to ask a new question where you state what you are trying to do with `CollectionService`. There is probably no reason to use `` in your case. Just make clear in that new question why you need to use a generic type parameter inside `CollectionService` and why this is not working for you. – Jesse de Wit Mar 27 '19 at 12:59
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

Siva Gopal
  • 3,474
  • 1
  • 25
  • 22