【问题标题】:Why I am unable to cast concrete generic to paremeter with interface?为什么我无法使用接口将具体泛型转换为参数?
【发布时间】:2014-09-01 11:49:53
【问题描述】:

我已经阅读了许多与此相关的问题,也许这是重复的,但我仍然无法理解这个概念。从我读到的内容来看,这与covariance and contravariance 有关。

我有这些接口和类:

public interface IBaseEntity
public interface IRepository<T> where T : IBaseEntity

public interface ITravelRequest : IBaseEntity
public interface IUser : IBaseEntity

public class TravelRequest : ITravelRequest
public class User:  IUser

我有这个控制器:

public TravelRequestsController(IRepository<ITravelRequest> repository, IRepository<IUser> userRepositor)

我正在尝试通过这样创建存储库来注入存储库:

var travelRequestRepository = new Repository<TravelRequest>(context);
var userRepository = new Repository<User>(context);

var controller = new TravelRequestsController(travelRequestRepository, userRepository);

但我收到此错误:

Error   4   Argument 1: cannot convert from
DAL.Repository<Elite.Models.TravelRequest.TravelRequest>' to
DAL.IRepository<Elite.Models.TravelRequest.ITravelRequest>' 

我怎样才能做到这一点?

更新 - 完整的 IRepository

 public interface IRepository<T> where T : IBaseEntity
    {
        IEnumerable<T> AsQueryable();

        IList<T> GetAll();
        IList<T> Find(Expression<Func<T, bool>> predicate);
        T Single(Expression<Func<T, bool>> predicate);
        T SingleOrDefault(Expression<Func<T,bool>> predicate);
        T First(Expression<Func<T, bool>> predicate);
        T GetById(int id);

        T Create();
        void Add(T entity);
        void Delete(T entity);
        void Update(T entity);
        void Save();
    }

我可以更改控制器签名,但我的最终目标是使用 Moq 之类的东西对其进行测试。

【问题讨论】:

  • 您对IUserITravelRequest 接口的意图是什么?你真的打算为IUser 接口使用不同的用户实现吗?不要忘记接口是用来抽象行为的。抽象数据没有用。我相信这两个接口是没用的。删除这些接口,您的问题就会消失。
  • 我拥有这些的主要原因是为了测试。你是对的,它们可能没用。我找到了解决问题的方法,但我想知道我最初打算做的事情是否可行。
  • 您的原始 IRepository 接口必须是不变的,但不能是协变或逆变的。因为在 C# 中,如果要使接口协变的类型参数 T,则必须指定 'out' 参数修饰符。使用该修饰符意味着只能在您的界面中输出 T 。添加/删除/更新违反了这些限制。逆变也一样,你必须指定'in'修饰符,但之后你不能有像 Single/First/GetById 这样的方法。您只能使用“in”或“out”,但不能同时使用。

标签: c# dependency-injection asp.net-mvc-5 covariance contravariance


【解决方案1】:

假设IRepository&lt;T&gt; 有一个Insert(T request) 方法。 Repository&lt;T&gt; 将此方法实现为Insert(T request),这意味着Repository&lt;TravelRequest&gt; 具有Insert(TravelRequest request) 方法签名,但它没有Insert(ITravelRequest request) 签名。

【讨论】:

  • 那么我怎样才能将泛型与接口一起使用。我需要使用最小起订量进行测试。
  • @Daniel:选项 1:认识到 TravelRequest 应该是一个 POCO,没有可变行为,因此不需要模拟。注入IRepository&lt;TravelRequest&gt; 并完全删除ITravelRequest 接口。选项 2:给 Repository&lt;&gt; 提供第二个泛型类型来表示它应该实现 IRepository&lt;&gt; 的类型。还有其他选择,但这完全取决于您的具体情况。
【解决方案2】:

要消除错误,请使用out 关键字声明接口。

public interface IRepository<out T> where T : IBaseEntity
{}

我能够成功编译以下代码。

    static void Main(string[] args)
    {
        var travelRequestRepository = new Repository<TravelRequest>();
        var userRepository = new Repository<User>();
        test(travelRequestRepository,userRepository);
        Console.ReadLine();


    }

    static void test(IRepository<ITravelRequest> repository, IRepository<IUser> userRepositor)
    {

    }


    public interface IBaseEntity{}

    public interface IRepository<out T> where T : IBaseEntity
    {}

    public class Repository<T>:IRepository<T> where T:IBaseEntity
    {

    }

    public interface ITravelRequest : IBaseEntity
    {}

    public interface IUser : IBaseEntity
    {}

    public class TravelRequest : ITravelRequest
    {}

    public class User : IUser
    { }

【讨论】:

  • 真正的IRepository&lt;T&gt;接口有当T协变时不能使用的方法(Add\Delete\Update)。
猜你喜欢
  • 2016-10-20
  • 1970-01-01
  • 1970-01-01
  • 2022-11-29
  • 2023-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多