C#类型转换的奇怪现象 - 将接口用作泛型类型

5

我刚遇到了一个类型转换的奇怪问题。我有类似下面代码:

interface IMyClass { }

class MyClass: IMyClass { }

class Main
{
  void DoSomething(ICollection<IMyClass> theParameter) { }

  HashSet<MyClass> FillMyClassSet()
  {
    //Do stuff
  }

  void Main()
  {
    HashSet<MyClass> classSet = FillMyClassSet();
    DoSomething(classSet);
  }
}

当执行DoSomething(classSet)时,编译器报错称无法将HashSet<MyClass>强制转换为ICollection<IMyClass>。这是为什么呢?HashSet实现了ICollection,MyClass实现了IMyClass,为什么转换就不合法呢?
顺便提一下,这个问题并不难解决,虽然有点棘手。
void Main()
{
  HashSet<MyClass> classSet = FillMyClassSet();
  HashSet<IMyClass> interfaceSet = new HashSet<IMyClass>();
  foreach(IMyClass item in classSet)
  {
    interfaceSet.Add(item);
  }
  DoSomething(interfaceSet);
}

对我来说,这个能够工作的事实使得无法投射更加神秘。

@Mehrdad已经回答了你的问题。但是,你能否不使用foreach,而是将FillMyClassSet()的返回类型更改为HashSet<IMyClass> - JMD
3个回答

8

这样做是行不通的,因为所有MyClass实例都实现了IMyClass并不意味着所有HashSet<MyClass>实例也都是HashSet<IMyClass>。如果这样做,你就可以:

ICollection<IMyClass> h = new HashSet<MyClass>();
h.Add(new OtherClassThatImplementsIMyClass()); // BOOM!

从技术上讲,这不起作用是因为C#(<= 3.0)的泛型是不变的。C# 4.0 引入了安全协变,但在这种情况下并没有帮助。然而,在接口中仅在输入或输出位置使用类型参数时,它确实有所帮助。例如,您将能够将一个HashSet<MyClass>作为IEnumerable<IMyClass>传递给某些方法。

顺便说一句,手动填充另一个HashSet的解决方法比这更容易:

var newSet = new HashSet<IMyClass>(originalSet);

如果你想将一个集合转换为 IEnumerable<IMyClass>,你可以使用 Cast 方法:

IEnumerable<IMyClass> sequence = set.Cast<IMyClass>(set);

如果我给自己更多的时间,我应该会意识到这一点。4.0版本的信息很有趣。所以如果我可以将其更改为DoSomething(IEnumerable<IMyClass> theParameter),在4.0版本中强制转换是否有效?也就是说,假设我确实应该使用IEnumerable。 - JamesH
是的,在4.0中这将起作用并且是安全的。在3.0中,您必须使用Cast方法(即使它是安全的,编译器和运行时也不知道它)。 - Mehrdad Afshari

2
这样的投射实际上是不安全的。考虑以下代码:
class MyOtherClass: IMyClass { }

void DoSomething(ICollection<IMyClass> theParameter) { theParameter.Add(new MyOtherClass(); } 

ICollection<MyClass> myClassSet = ...;
DoSomething(classSet);   //Oops - myClassSet now has a MyOtherClass

您所要求的是协变性,它在C# 4中对于只读的IEnumerable<T>可用。

0

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