【问题标题】:How to cast object1<object2> to interface of object1<interface of object2>?如何将 object1<object2> 转换为 object1<object2> 的接口?
【发布时间】:2013-10-14 07:51:57
【问题描述】:

假设我有这样的安排:

    public interface ICreatable
    {
        int CreatedByUserId { get; set; }
    }

    public class Unicorn : ICreatable
    {
        public int CreatedByUserId { get; set; }
    }

    public interface ICrudService<T>
        where T : class, ICreatable
    {
        T DoSomething(T t);
    }

    public class UnicornService : ICrudService<Unicorn>
    {
        public Unicorn DoSomething(Unicorn unicorn)
        {
            var createdByUserId = unicorn.CreatedByUserId;

            // ...

            return unicorn;
        }
    }

然后像这样使用它:

    static void Main(string[] args)
    {
        var unicorn = new Unicorn();
        var unicornService = new UnicornService();

        unicornService.DoSomething(unicorn);
    }

这运行良好。但是,假设我想将unicornService 转换为ICrudService 的接口类型以及它的接口类型的泛型类型:

        var crudService = unicornService as ICrudService<ICreatable>;

我遇到了问题。看起来是这样的:

unicornService as ICrudService<Unicorn> --> casts is fine
unicornService as ICrudService<ICreatable> --> casts to null

似乎自从Unicorn 派生自ICreatable 并且自ICrudService&lt;T&gt; where T: class, ICreatable 以来,它应该没有问题解决这个问题。我的搜索开始引导我进入协变和逆变,但我在那个级别迷失了。

如何将crudService 转换为ICrudService&lt;ICreatable&gt;

更新:

像这样使用协方差:

    public interface ICrudService<out T>

然后让智能感知说“无效的方差:类型参数 'T' 必须在 'ICrudService.DoSomething(T)' 上逆变有效。'T' 是协变的。”这是如何工作的?

【问题讨论】:

  • 尝试使用ICrudService&lt;out T&gt; 并查看out 以了解它的含义。
  • 谢谢@AlessandroD'Andria 我更新了这个问题。使用 out 关键字会导致另一个问题。我忽略了一些简单的事情吗?
  • 您发布的代码与out T 参数一起工作正常,因此问题出在其他地方。
  • @AlessandroD'Andria 不,它没有。它使用T 作为方法的参数和返回值,这意味着它相对于T 是不变的,既不能是协变的也不能是逆变的。
  • @Sevy 你说得对,我只看到编辑...

标签: c# generics interface covariance contravariance


【解决方案1】:

ICrudService&lt;Unicorn&gt; 不能被视为ICrudService&lt;ICreatable&gt;

ICrudService&lt;Unicorn&gt; 对象只允许接受Unicorn 类型的参数或Unicorn 的子类型,但ICrudService&lt;ICreatable&gt; 可以接受SomeOtherTypeOfICreatable 的参数。

UnicornService 类型允许使用特定于 Unicorn 的成员,而不仅仅是 ICreatable,因为这是它限制其功能的类型。该限制禁止它满足更通用接口的 API。

所以,简而言之,这是不可能的。

【讨论】:

    【解决方案2】:

    您应该将 DoSomething 更改为接受 ICreatable 而不是 T 以使用 out T 修饰符:

    public interface ICrudService<out T>
        where T : class, ICreatable
    {
        T DoSomething(ICreatable t);
    }
    
    public class UnicornService : ICrudService<Unicorn>
    {
        public Unicorn DoSomething(ICreatable t)
        {
            var unicorn = (Unicorn)t;
            var createdByUserId = unicorn.CreatedByUserId; // or t.CreatedByUserId
    
            // ...
    
            return unicorn;
        }
    }
    

    请注意,如果将非 Unicorn 传递给 UnicornService,此代码将引发异常。

    【讨论】:

    • 这实际上只是删除了 OP 代码中的所有(有价值的)类型安全性。如果DoSomething 确实需要输入类型为T,而不是ICreatable,那么不应该进行此更改。不能将其转换为ICrudSercice&lt;ICreatable&gt; 的原因是它在逻辑上没有按照接口所说的实现它的对象必须做的事情,因此您不能将其转换为该接口的事实是一件好事。这意味着您的课程并未声称拥有它实际上没有的功能。
    • @Sevy 你是对的,这就是协方差的重点,但是从上述方法中删除Unicorn 的演员表并且只使用ICreatable 可能可以满足OP 的需求.
    • 是的,但是 OP 肯定需要一个 ICrudSercice,所以返回 T 或参数 T 都应该替换为 ICreatable。
    猜你喜欢
    • 1970-01-01
    • 2014-11-13
    • 2020-07-22
    • 2015-01-26
    • 2018-01-06
    • 2018-01-21
    • 2013-11-13
    • 2014-05-09
    • 1970-01-01
    相关资源
    最近更新 更多