.NET 4.0 强制转换泛型接口

4

我有两个接口,其中一个是通用接口,只允许派生自第二个接口的类型。它们看起来像这样:

public interface IProvider<T> where T : IContent
{
    T getContent(int i);
    void addContent(T content);
}
public interface IContent
{
    string whatIAm();
}

当然,我的真正接口更加复杂,但足以展示我的问题。现在,我为每个接口都有一个具体的类:

public class Provider : IProvider<FileContent> 
{
    public FileContent getContent(int i)
    {
        return null;
    }
    public void addContent(FileContent content)
    {
    }
}

public class FileContent : IContent{
    public string whatIAm(){
        return "FileContent";
    }
}

在我的代码中,我想使用引用类型"IProvider",但是转换出错了...请看这个例子:

 static void Main(string[] args)
    {
        Provider p = new Provider(); //works
        IProvider<FileContent> pp = p as IProvider<FileContent>; //also works
        IProvider<IContent> ppp = pp as IProvider<IContent>; //fails :(
    }

ppp 总是为 null。我需要做哪些更改才能使此转换工作?提前感谢。


1
这可能是Casting an object to a generic interface的重复。 - Paolo Moretti
3个回答

6
类型参数必须完全匹配。 IProvider&lt;IContent&gt; 是与 IProvider&lt;FileContent&gt; 不同的类型,它们之间没有继承关系。
假设您有一个来自于 IProvider&lt;FileContent&gt;IProvider&lt;IContent&gt; ppp,并且开发人员尝试 ppp.addContent(someOtherContentThatIsNoFileContent)。对于 IProvider&lt;IContent&gt;,该语句是有效的,但它会破坏类型安全性,因此不允许这样的转换是正确的做法。
在某些情况下,用于泛型类型参数的协变性和逆变性 可以允许类似这样的操作,但由于您的接口将类型参数同时用作输入和输出参数,因此无法直接使用它。 编辑: 查看 IEnumerable 的定义:
public interface IEnumerable<out T> 

所以,您知道IEnumerable仅将T用作输出参数(不能添加项,只能枚举它们),而out关键字指定T是协变的。 因此,您可以这样做:

IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;

如果您想这样做,您需要从接口中删除add方法。对于输入参数和泛型类型参数上的in关键字也是如此。

然后您的接口将如下所示:

public interface IProvider<out T> where T : IContent
{
    T getContent(int i);
}

正如我在问题中已经写过的: 我需要改变什么才能让这个强制转换起作用?我已经尝试删除IProvider接口中的两种方法中的一种,但无论我删除哪一个,强制转换仍然不起作用 :( - user1531730
@user1531730 评论太长了,我更新了我的答案。 - Botz3000
@user1531730 很高兴听到这个好消息。 :) 我猜你需要将你的界面分成两个接口。 - Botz3000

3
这不是 C# 中泛型的工作方式。一个 IProvider<FileContent> 的泛型并不是 IProvider<IContent> 的子类型。

1

你写了

Provider p = new Provider(); //works
IProvider<FileContent> pp = p as IProvider<FileContent>; //also works
IProvider<IContent> ppp = pp as IProvider<IContent>; //fails :(

假设这三个都可以工作,那么我们可以写出以下代码:
ppp.addContent(new NonFileContent());

其中NonFileContent是实现IContent接口的类,但不是派生自FileContent

现在,想象一下以下调用会发生什么:

FileContent fc = pp.getContent(0);

刚刚添加的对象应该被返回。但是,它是一个 NonFileContent 实例,而不是 FileContent 实例。因此,从一个返回值必须是 FileContent 实例的方法中返回这个对象是不可能的,这就是为什么编译器一开始就不认为 pp 和 ppp 是赋值兼容的原因。

好的,我想开始明白为什么它不起作用了。但是参考Botz3000发布的链接,如果我从接口中删除void addContent(T content);方法,应该是可行的 - 但那也不起作用。 - user1531730

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