【问题标题】:Covariance Contravariance Generics Interfaces - why doesn't this work?协方差逆变泛型接口 - 为什么这不起作用?
【发布时间】:2016-03-22 13:50:29
【问题描述】:

提前谢谢你。这是我的代码:

public class ApplicationUser : IdentityUser
{
    public ApplicationUser()
    {

    }
}

public class AppUserManager : UserManager<ApplicationUser>
{
...
}

public interface IOwinManager
{
    UserManager<IdentityUser> UserManager { get; }
}

为什么这不起作用?

public class OwinManager : IOwinManager
{        
    public UserManager<IdentityUser> UserManager
    {
        get { return new AppUserManager(); }
    }
}

既然ApplicationUser继承自IdentityUser,AppUserManager继承自UserManager,为什么不接受组合泛型?谢谢!

【问题讨论】:

  • UserManager&lt;&gt; 是一个类,而不是一个接口吗?仅仅因为“一个ApplicationUser 是一个 IdentityUser”并不意味着“一个UserManager&lt;ApplicationUser&gt; 是一个UserManager&lt;IdentityUser&gt;"。除非类型 UserManager&lt;&gt; 已在其类型参数中声明为 covariant。在当前版本的 C# 中,只有 interface 类型和 delegate 类型可以声明为协变(out 类型参数上的修饰符)。 class 类型和 struct 类型不能。

标签: c# generics interface covariance contravariance


【解决方案1】:

不支持类的泛型类型参数的逆变和协变。

简化您的问题:

// Compiler error!
UserManager<IdentityUser> userManager = new AppUserManager();
  • AppUserManager 继承 UserManager&lt;ApplicationUser&gt;
  • 因此,您尝试在UserManager&lt;IdentityUser&gt; 引用上设置UserManager&lt;ApplicationUser&gt; 派生引用。这就是问题!它们是不同的类型。

OP 说...

这基本上意味着,我不能使用具体的类及其 接口中的泛型,并期望它们由它们的实现 孩子们?

接口支持变化。因此,您可以按如下方式设计您的界面:

public interface IOwinManager<out TUser, out TManager>
    where TUser : IdentityUser 
    where TManager : UserManager<TUser>
{
    TManager UserManager { get; }
}

...一旦你实现了这个接口,你的实现将声明一个具体的TManager 类型的属性。

【讨论】:

  • 这基本上意味着我不能在接口中使用具体类及其泛型并期望它们由他们的孩子实现?
【解决方案2】:

Matias 的回答很好;我想我会添加更多的上下文。让我们再次简化您的示例:

class Animal {} // IdentityUser
class Tiger : Animal {} // ApplicationUser    
class Giraffe : Animal {} // some other kind of user
class Cage<T> where T : Animal {} // UserManager
class SpecialTigerCage : Cage<Tiger> {} // AppUserManager

现在的问题是“为什么这种转换是非法的?”

Cage<Animal> cage = new SpecialTigerCage();

现在应该很清楚为什么这是非法的。 你可以把长颈鹿放进可以放动物的笼子里,但是如果你把长颈鹿放进一个特殊的老虎笼子里,长颈鹿就不会很高兴了。

类型系统无法令人满意地证明您不会将长颈鹿放入老虎笼中,因此它不允许转换。

正如其他人指出的那样,C# 确实支持接口和委托的这种协变转换,它可以证明你不会把长颈鹿放进老虎笼子里。 IEnumerable&lt;T&gt; 例如是协变的。在需要动物序列的情况下可以使用老虎序列,因为IEnumerable&lt;T&gt; 可证明没有方法可以将长颈鹿插入序列中。

【讨论】:

  • 谢谢 - 这确实让它更清楚了一点,但我仍然感到困惑。也许我自己很困惑,因为我希望 - 然后我应该在一个笼子tigercage 实现中添加一个长颈鹿,然后如果我明确地发送我的 where T: Animal,我只希望调用 Animal 方法。所以,我认为这是可能的,只要我明确声明 T 是动物类型。和你一样吗?这应该是合法的,如果它把所有东西都改成动物?
  • @EvangelosAktoudianakis:将所有元素都投射到 Animal 中时,在拉出元素时效果很好(我想要一个 Animal,得到了一个 Tiger),但在放入元素时却不行(这个系列想要一个 Tiger,我给了它一个 an动物)。为您的 Cage 提供两个接口很容易,一个用于放入动物的接口和一个用于拉出东西的接口(例如,IEnumerable,但您可以定义自己的)。如果您希望类型系统处理此类转换,请在函数签名中使用接口。
  • @EvangelosAktoudianakis:问题不在于编译时类型为 T 的对象会调用哪些方法。你说 Animal 的方法会被调用是正确的,但这不相关。问题是,假设Cage&lt;T&gt; where T : Animal 有一个方法AddAnimal(T)。所以Cage&lt;Animal&gt; 有一个方法AddAnimal(Animal)。但是SpecialTigerCage 只实现了AddAnimal(Tiger);它无法完成Cage&lt;Animal&gt; 所需的所有职责,因此不能作为一个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-02
相关资源
最近更新 更多