0
abstract class A<T> {
  List<T> Items { get; set; }
}

class B {}
class C : A<B> {}

class D : B {}
class E : A<D> {}

static class X {
  public A<B> GetThing(bool f) {
    return f ? new E() : new C();
  }
}

无法确定条件表达式的类型,因为“ConsoleApplication4.E”和“ConsoleApplication4.C”之间没有隐式转换

现在我明白了“为什么”(我认为),但我看不出如何编译。我想我必须创建一个定义某种方差并从中继承的接口,但我不确定。但无论如何,E()都应该继承自C().

有接盘侠吗?

TIA

4

3 回答 3

15

要在 C# 中使用泛型变量,您必须满足以下所有条件:

  1. 使用 C# 4
  2. 变化的类型必须是泛型接口或泛型委托
  3. 类型参数必须标记为“in”(逆变)或“out”(协变)
  4. 类型参数注释必须产生一个在所有可能的操作中可证明是类型安全的类型
  5. “源”类型参数和“目标”类型参数之间必须有标识或引用转换。

您的程序满足条件 1 和条件 5,但不满足条件 2、3 或 4。

没有办法得到你想要的方差,因为你想要一些违反条件 4的东西。观察当我们满足除条件 4 之外的所有条件时会发生什么:

// Suppose this declaration were legal:
interface IMyCollection<out T> 
{
  List<T> Items { get; set; } 
}

class Animal {}
class AnimalCollection : IMyCollection<Animal> 
{ 
  public List<Animal> { get; set; }
}

class Giraffe : Animal {}
class GiraffeCollection : IMyCollection<Giraffe> 
{
  public List<Giraffe> { get; set; } 
}

static class X 
{
  public IMyCollection<Animal> GetThing() 
  {
    // IMyCollection is covariant in T, so this is legal.
    return new GiraffeCollection(); 
  }
}

class Tiger : Animal {}

...

IMyCollection<Animal> animals = X.GetThing(); 
// GetThing() actually returns a GiraffeCollection
animals.Items = new List<Animal>() { new Tiger(); }

长颈鹿集合现在包含一个包含老虎的动物列表。

如果要使用方差,则必须是类型安全的。因为编译器无法确定变体注解是类型安全的,所以它拒绝 IMyCollection 的声明。

让我们看看我们如何做到这一点并保证类型安全。问题是 Items 可以通过接口写入。

interface IMyCollection<out T> 
{
  IEnumerable<T> Items { get; }
}

class Animal {}
class AnimalCollection : IMyCollection<Animal> 
{ 
  public IEnumerable<Animal> { get { yield return new Tiger(); } }
}

class Giraffe : Animal {}
class GiraffeCollection : IMyCollection<Giraffe> 
{
  public IEnumerable<Giraffe> { get { yield return new Giraffe(); } } 
}

static class X 
{
  public IMyCollection<Animal> GetThing() 
  {
    return new GiraffeCollection();
  }
}

class Tiger : Animal {}

...

MyCollection<Animal> animals = X.GetThing(); 
// GetThing() actually returns a GiraffeCollection
foreach(Animal animal in animals.Items) { ... } 
// Items yields a giraffe, which is an animal

完美的类型安全。这将是一个合法的 C# 4 程序。

如果您对 C# 4 中协变和逆变的设计细节感兴趣,您可以考虑阅读我关于该主题的十几篇文章。你可以在这里找到它们:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

请注意,这些是按从最近到最近的顺序列出的;从底部开始。

如果您特别对确定接口注释何时有效的规则感兴趣,请参阅这篇文章(我刚刚发现并修复了一些错误,所以我很高兴我们进行了这次对话。)

http://blogs.msdn.com/b/ericlippert/archive/2009/12/03/exact-rules-for-variance-validity.aspx

于 2011-04-07T14:11:11.230 回答
3

E和之间没有关系C。因此,您的返回类型必须是A<B>可以被视为这些类型的共同父级的类型。还A需要是一个接口,其中定义了通用参数out才能使其工作:

interface A<out T> { }

class B { }
class C : A<B> { }

class D : B { }
class E : A<D> { }

static class X
{
    public static A<B> GetThing(bool f)
    {
        if (f)
        {
            return new E();
        }
        return new C();
    }
}

无法使其与三元运算符 ( ?:)一起使用。编译器根本不喜欢它。


更新:

在@Eric Lippert 在评论部分的出色评论之后,您可以通过转换为来使用三元运算符A<B>

public static A<B> GetThing(bool f)
{
    return f ? (A<B>)new E() : new C();
}
于 2011-04-07T11:38:25.230 回答
0

这是行不通的,因为C并且E没有共同的父母。对于这样的事情,您需要创建一个公共父类型,无论是类还是接口。

于 2011-04-07T11:38:56.010 回答