接口 无法隐式转换类型

4
我有以下代码。我有两个主接口IWatchIWatchService。最初Watch()IWatchService中,没有IWatch,但由于CollectionService无法使用Watch()方法,因此我决定(ISP)另外创建IWatch接口。在CollectionService中,我想在构造函数中传递DatabaseWatchServiceRemoteFilesWatchService,因此我将参数类型放入构造函数中IWatchService<IEntity> watchService,然而当在DoIt()方法中初始化fileWatcherServiceCsv变量时,它说:

无法隐式转换类型 'RemoteFilesWatchService' 为 'IWatchService'。是否存在显式转换?

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; }
}

1
你没有提供所有的代码。请提供一个能够演示你问题的 [mcve]。 - rory.ap
即使您进行了编辑,它仍无法编译。 它缺少许多定义:IEntityIDatabaseEntityIFileEntity等等。 - rory.ap
1
好的,我放了几乎所有的代码。希望现在足够检查问题了。对我来说应该可以工作。嗯。 - Henry
你编辑问题的工作做得很好。虽然我还没有再次测试代码,但看起来它都在那里了,或者几乎都在。在继续提问之前,我建议你遵循这里给出的建议:阅读[帮助]和特别是[提问]中的资源。 - rory.ap
创建MVC的快速指南:步骤1. 将您的代码粘贴到一个空的Visual Studio项目中。它是否编译成功(或生成了您所询问的错误)?如果没有,请添加更多的代码。如果必须添加对第三方库的引用,请使用这些库标记您的问题,如果该库本身不相关,则删除它们或将其替换为存根。如果需要您自己的库,请内联它们。步骤2. 删除任何不相关的代码。如果一个类是必要的但不相关,请用存根类替换它。是的,这个过程需要一个小时。请务必这样做。 - Brian
3个回答

9

这个问题几乎每天都会被发布。再说一遍!

一个苹果盒子不是水果盒子。为什么呢?

你可以将香蕉放入一个水果盒子中,但你不能将香蕉放入一个苹果盒子中,因此,一个苹果盒子和一个水果盒子的操作不同,所以它们不同。同样,一个水果盒子也不是一个苹果盒子。

你正尝试使用 IWatchService (盒子) 里的 IFileEntity (苹果) 作为 IEntity (水果) 的 IWatchService,而这是不合法的。

现在,你可能会注意到在C#中,你可以使用 IEnumerable<Apple> 替代 IEnumerable<Fruit>。这完全没有问题,因为没有办法将香蕉放入 IEnumerable<Fruit>。在 IEnumerable<T>IEnumerator<T> 的每个成员中,T 是在而不是在

如果你处于这种情况下,那么你可以将你的接口标记为

interface IWatchService<out T> ... 

编译器将验证接口中的每个T是否在“out”位置使用,然后允许您想要的转换。

这种转换称为泛型协变转换,它仅在以下情况下起作用:

  • 泛型类型是接口或委托
  • 类型参数标记为out,并且编译器验证其安全性
  • 不同的类型(例如水果和苹果)都是引用类型。例如,您不能进行涉及int和object的协变转换。

@Henry:一箱苹果不是一箱水果。可观察集合不适用于协变,因为在可观察集合中,T 可以同时进出。如果你将其改为仅允许 T 出的类型,例如 IEnumerable<T>,那么它就是协变有效的。 - Eric Lippert
嗯,现在我明白了,我需要为我的属性只放置 { get; }。问题是,以后在 DatabaseWatchService 或 RemoteFileWatchService 类中,当我想要使用 MatchingEntires 时,例如 Count()、Add() 或其他任何东西,它不再可用了。如何解决这个问题?到目前为止,感谢您在这里的贡献。 - Henry
“每个接口中的T都处于“out”位置”,这是什么意思?这意味着使用‘T’的接口成员要么是返回’T‘或者以out标记为参数传递‘T’的方法,要么是只读属性或字段。那么如果是一个带有‘T’参数的构造函数呢? - rory.ap
@rory.ap:不,返回 T 是可以的。不幸的是,out 参数被认为是输入有两个原因:(1) 因为你可以从 out 参数中读取,(2) 因为从运行时的角度来看,outref 是一样的东西,而 ref 是一个输入。由于只有接口和委托可以是变体,所以没有字段。接口的属性必须是只读的。委托和接口没有构造函数。 - Eric Lippert
@rory.ap:关于为什么在“out T”变体接口中out T参数不合法的详细解释,请参见https://dev59.com/ZXE85IYBdhLWcg3wOw_s#2877515。 - Eric Lippert
显示剩余3条评论

5
您的RemoteFilesWatchService实现了接口IWatchService<IFileEntity>,而您的CollectionService期望一个IWatchService<IEntity>。这两种类型是不同的,所以它无法转换。 修改您的CollectionService以接受IWatchService<IFileEntity>,或使RemoteFilesWatchService实现IRemoteFilesWatchService<IEntity>。或者在CollectionService中使用非泛型接口。
您不能拥有一个IWatchService<IFileEntity>并将其视为IWatchService<IEntity>。例如,将其与List<T>进行比较。您不能指望能够做到这一点:
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());

是的,如果你有一个 List<Animal>,你就可以添加 BirdElephant,因为它们都是动物。这样做很有道理,不是吗? - Jesse de Wit
1
但有趣的是,如果我将两只鸟添加到动物列表中,我也应该能够将所有鸟放入动物列表中,这意味着填满它,或者我应该像这样阅读它:List <Animal> animals = birds; 就像替换类型本身一样吗? - Henry
1
在这种情况下,List<Animal> animals = birds 是一个隐式转换。因此,我们将 List<Bird> 转换为 List<Animal>(不可能)。在底层它仍然是相同的 List<Bird>,这就是为什么你不能向其中添加 Elephant,但由于我们将其视为 List<Animal>,似乎可以这样做。希望这回答了你的问题,否则我们应该开个聊天窗口。 - Jesse de Wit
对我来说不可能聊天(声誉或者叫什么都无所谓),你能打开聊天室让我加入,讨论一些事情吗?如果可以的话。 - Henry
我建议您提出一个新问题,说明您想要使用CollectionService做什么。在您的情况下可能没有理由使用<out T>。只需在新问题中清楚地说明为什么需要在CollectionService内部使用泛型类型参数以及为什么这对您不起作用即可。 - Jesse de Wit
显示剩余6条评论

0

稍微更改一下以利用方差的支持,应该可以按照以下方式解决您的问题:

    public interface IEntity
    {

    }

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

你可以在泛型接口中了解更多关于方差的知识这里


哦!来啦... @Eric已经在这里了,提供了一个好的解释! - Siva Gopal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接