首页 文章

为什么C#编译器在从不同的基类派生时会抱怨“类型可能统一”?

提问于
浏览
70

我当前的非编译代码与此类似:

public abstract class A { }

public class B { }

public class C : A { }

public interface IFoo<T>
{
    void Handle(T item);
}

public class MyFoo<TA> : IFoo<TA>, IFoo<B>
    where TA : A
{
    public void Handle(TA a) { }
    public void Handle(B b) { }
}

C#编译器拒绝编译它,引用以下规则/错误:

'MyProject.MyFoo <TA>'无法实现'MyProject.IFoo <TA>'和'MyProject.IFoo <MyProject.B>',因为它们可能会统一某些类型参数替换

我理解这个错误意味着什么;如果 TA 可能是任何东西,那么它在技术上也可能是 B ,这会在两个不同的 Handle 实现中引入歧义 .

但TA can't 可以做任何事情 . 根据类型层次结构, TA can'tB - 至少,我认为不行 . TA 必须派生自 A ,它不是从 B 派生的,显然在C#/ .NET中没有多类继承 .

如果我删除泛型参数并将 TA 替换为 C ,甚至 A ,则会编译 .

那么为什么我会收到这个错误?它是编译器中的错误还是一般的非智能,还是我还缺少其他东西?

是否有任何解决方法或我只是必须为每个可能的 TA 派生类型重新实现 MyFoo 泛型类作为单独的非泛型类?

7 回答

  • 2

    这是C#4规范第13.4.2节的结果,该规范规定:

    如果从C创建的任何可能的构造类型将类型参数替换为L后,导致L中的两个接口相同,则C的声明无效 . 在确定所有可能的构造类型时,不考虑约束声明 .

    注意那里的第二句话 .

    因此,它不是编译器中的错误;编译器是正确的 . 有人可能会说这是语言规范中的一个缺陷 .

    一般而言,在几乎所有必须推断出关于泛型类型的事实的情况下,忽略约束 . 约束主要用于确定泛型类型参数的有效基类,而其他几乎没有 .

    不幸的是,正如您所发现的那样,这有时会导致语言不必要的严格 .


    通常两次实现"the same"接口的代码异味很糟糕,在某种程度上只能通用泛型类型参数来区分 . 例如,拥有 class C : IEnumerable<Turtle>, IEnumerable<Giraffe> 是什么奇怪的事情 - 什么是C,它既是一系列的乌龟,又是一系列的长颈鹿,同时呢?你能描述一下你想在这里做的实际事情吗?可能有更好的模式来解决真正的问题 .


    实际上,您的界面与您描述的完全一致:

    interface IFoo<T>
    {
        void Handle(T t);
    }
    

    然后接口的多重继承提出了另一个问题 . 你可能会合理地决定使这个界面逆变:

    interface IFoo<in T>
    {
        void Handle(T t);
    }
    

    现在假设你有

    interface IABC {}
    interface IDEF {}
    interface IABCDEF : IABC, IDEF {}
    

    class Danger : IFoo<IABC>, IFoo<IDEF>
    {
        void IFoo<IABC>.Handle(IABC x) {}
        void IFoo<IDEF>.Handle(IDEF x) {}
    }
    

    而现在事情变得非常疯狂......

    IFoo<IABCDEF> crazy = new Danger();
    crazy.Handle(null);
    

    Handle的哪个实现被称为???

    有关此问题的更多想法,请参阅此文章和评论:

    http://blogs.msdn.com/b/ericlippert/archive/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity.aspx

  • 0

    显然,它是在Microsoft Connect中讨论的设计:

    解决方法是,将另一个接口定义为:

    public interface IIFoo<T> : IFoo<T>
    {
    }
    

    然后将其实现为:

    public class MyFoo<TA> : IIFoo<TA>, IFoo<B>
        where TA : A
    {
        public void Handle(TA a) { }
        public void Handle(B b) { }
    }
    

    它现在编译好了,mono .

  • 0

    在这里看到我对基本相同问题的回复:https://stackoverflow.com/a/12361409/471129

    在某种程度上,这可以做到!我使用差异化方法,而不是限制类型的限定符 .

    它没有统一,事实上它可能比它更好,因为你可以分开挑逗单独的接口 .

    在这里查看我的帖子,在另一个上下文中有一个完整的例子 . https://stackoverflow.com/a/12361409/471129

    基本上,你要做的是为IIndexer添加另一个类型参数,使其成为 IIndexer <TKey, TValue, TDifferentiator> .

    然后当你使用它两次时,你将“First”传递给第一次使用,将“Second”传递给第二次使用

    所以,类Test成为:class Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>

    因此,你可以做 new Test<int,int>()

    第一和第二是微不足道的:

    interface First { }
    
    interface Second { }
    
  • 8

    我知道自帖子发布以来已经有一段时间了,但对于那些通过搜索引擎来寻求帮助的人来说 . 注意,'Base'代表TA和B的基类,如下所示 .

    public class MyFoo<TA> : IFoo<Base> where TA : Base where B : Base
    {
        public void Handle(Base obj) 
        { 
           if(obj is TA) { // TA specific codes or calls }
           else if(obj is B) { // B specific codes or calls }
        }
    
    }
    
  • 1

    现在猜一下......

    不能在外部程序集中声明A,B和C,在编译MyFoo <T>之后类型层次结构可能会发生变化,从而给世界带来浩劫吗?

    简单的解决方法是实现Handle(A)而不是Handle(TA)(并使用IFoo <A>而不是IFoo <TA>) . 无论如何,你不能使用Handle(TA)比A中的访问方法(由于A:TA约束)做得更多 .

    public class MyFoo : IFoo<A>, IFoo<B> {
        public void Handle(A a) { }
        public void Handle(B b) { }
    }
    
  • 0

    嗯,这个怎么样:

    public class MyFoo<TA> : IFoo<TA>, IFoo<B>
        where TA : A
    {
        void IFoo<TA>.Handle(TA a) { }
        void IFoo<B>.Handle(B b) { }
    }
    
  • 45

    如果你在一个基类上放置一个接口,你可以在雷达下偷偷摸摸 .

    public interface IFoo<T> {
    }
    
    public class Foo<T> : IFoo<T>
    {
    }
    
    public class Foo<T1, T2> : Foo<T1>, IFoo<T2>
    {
    }
    

相关问题