当另一个接口设置为仅时,为什么属性被认为是不明确的?

时间:2013-12-05 23:14:33

标签: c# compiler-errors

在以下代码中:

interface IGet { int Value { get; } }

interface ISet { int Value { set; } }

interface IBoth : IGet, ISet { }

class Test
{
    public void Bla(IBoth a)
    {
        var x = a.Value; // Error: Ambiguity between 'IGet.Value' and 'ISet.Value'
    }
}

我收到错误:

  

'IGet.Value'和'ISet.Value'之间的歧义

为什么编译器无法确定访问的属性必须来自IGet

编辑 - 我猜测编译器首先尝试确定正在访问哪个属性,然后才检查它是get还是set。问题是 - 为什么?为什么不排除那些不提供吸气剂的候选人呢?为什么不解析实际的get / set方法并忽略它是属性的事实呢?

更新Some more code using methods instead of properties,此问题不存在。属性并不完全像一对方法。

3 个答案:

答案 0 :(得分:14)

  

我猜测编译器首先尝试确定正在访问哪个属性,然后才检查它是get还是set。

你的猜测是正确的。

  

问题是 - 为什么?为什么不排除那些没有提供吸气剂的候选人?为什么不解决实际的get / set方法并忽略它属于某个属性的事实?

     

编译器可以解析所有候选人,然后排除那些不能提供的人 - 为什么不这样做呢? - sinelaw

     

因为语言设计师没有这样设计吗? - 罗伯特哈维

     

@sinelaw因为那不是C#语言的定义方式。它不可能无法完成,只是它没有完成。 - user2864740

     

我确定他们是这样设计的(他们不太可能忽视这种情况) - 但是推理是什么? - sinelaw

     @sinelaw大概是因为他们没有感觉到这种功能带来的好处会增加开发它的复杂性。 - p.s.w.g

pswg在这里是正确的轨道,但我们可以更具体。

这里的基本设计原则是分析从内到外,而不考虑"上下文提示"。当表达式的含义取决于它的直接上下文时,它对于读者来说既困惑又对编译器和IDE开发人员来说很难。我们想要做的是明确地计算每个表达式的含义,然后验证它在其上下文中是否有效。我们不想走另一条路,然后说'嗯,这个表达式含糊不清,所以让我用上下文作为线索"。

更具体地说:首先,编译器必须确定a的含义,然后确定a.Value的含义,然后确定赋值是否合法。编译器没有说"好吧,我无法弄清楚a.Value两个属性中的哪一个含义,因为它含糊不清,但是我会假装我确实想到了当我意识到我在任务的价值方面并且只有其中一个具有价值时,我会回过头来补课。编译器也没有说"当我在一个任务的左侧时,我将使用一个查找算法,而当我在右侧时,我将使用另一个查找算法"

(旁白:当然,我们在技术上讲这里的任务;我们在一个隐式类型的本地的初始化器中,它不被归类为赋值运算符的用法。但它在逻辑上等同于此,所以我们将在没有进一步评论的情况下通过。)

这个一般规则有一些例外,针对特定的常见情况。编译器确实知道例如在a.B()形式的表达式中B需要是可调用的东西;成员查找算法会自动拒绝不可调用的成员而不会出错。 Lambdas当然完全拒绝这个原则; lambda的含义完全取决于它的上下文。完成这项工作需要花费大量的工作 - 这是我对C#3的一个特性 - 我们进行了大量投资以确保算法在常见场景中具有高性能。无论何时,当你从外到内,内到外的同时,你最终都处于潜在的指数状态,你必须做出一切可能的试验绑定,然后选择有效的独特试验。对于像类型推断的lambdas这样的强大功能,这个成本是值得的。使其他形式的上下文敏感性工作,特别是对于您描述的模糊场景,并不是花费有限预算的好方法。

  

所以我的回答中的代码示例有效,因为它"合并"将两个属性定义合并为一个调用(消除了编译器的歧义),同时为getter和setter实现了两个接口契约?罗伯特哈维

为了澄清,Robert删除的答案中的代码是:

public class GetSet : ISet, IGet
{
    public string Value { get; set; }
}
...
getSet.Value = "This is a test";
Debug.Print(getSet.Value); //Prints "This is a test"
罗伯特我不确定我理解你的问题。您的代码有效,因为首先,ISetIGet的合同已经完成。类GetSet具有每个所需的所有成员以及明确的映射。第二,因为您的呼叫站点根本不使用接口;它只是直接调用类的成员。为什么它不起作用?

现在解决已删除答案中的一点:

  

只是拥有另一个继承原始二者的界面是不可行的,因为没有要绑定的支持字段。

不,这不是一个正确的分析。这与属性是否实际实现为编译器生成的字段无关。请记住,接口上的属性只是定义get_Valueset_Value方法的奇特方式。只要实现类中存在具有所需方法的属性,就满足接口要求。 该属性的实现方式取决于该类。

  

一个类属性满足两个不同合同的接口。

是的!这不是问题。只要可以明确地确定从接口成员到类/结构成员的映射,就可以了。例如:

interface IFoo
{
    void M();
}

interface IBar
{
    void M();
}

class C : IFoo, IBar 
{ 
    public void M() { } 
}

M可以同时执行IFoo.MIBar.M双重任务。

当您遇到麻烦时,无法轻易确定哪种方法与界面匹配。有关详细信息,请参阅我关于该主题的文章:

http://blogs.msdn.com/b/ericlippert/archive/2006/04/05/odious-ambiguous-overloads-part-one.aspx

http://blogs.msdn.com/b/ericlippert/archive/2006/04/06/odious-ambiguous-overloads-part-two.aspx

对于一些有趣的相关恶作剧,看到这个问题和答案,卢西恩和我都说过:

Generic type parameter covariance and multiple interface implementations

答案 1 :(得分:7)

我同意你的分析。似乎编译器需要在之前解析符号a.Value ,它会分析您如何使用它将属性getter转换为对get_Value的调用。

值得注意的是,如果你这样做:

public void Bla(ISet a)
{
    var x = a.Value; 
}

您没有收到'不包含定义'错误。你明白了:

  

属性或索引器'ISet.Value'不能在此上下文中使用,因为它缺少get访问器

编译器找到了符号,将其绑定到ISet.Value,之后才抱怨它是如何被使用的(因为ISet没有提供getter)。

答案 2 :(得分:5)

.NET的创建者已经决定,属性本身应该是一种特殊的实体,而不是仅仅允许对具有“属性”属性的适当命名方法进行替代形式的调用。这有时很烦人;一个可能有用的方面是它确保防止代码覆盖属性getter的可能性,而没有意识到属性有一个setter,或者覆盖属性setter而没有意识到getter做了其他事情而不是返回预期的支持领域。

如果希望有一组协变的列表接口,则需要定义:

interface IReadableList<out T> {
    T this[int index] { get; }
}

interface IWritableList<in T> {
    T this[int index] { set; }
}

interface IMutableList<T>: IReadableList<T>, IWritableList<T> {
    new T this[int index] { get; set; }
}

反过来,代码必须定义所有三个属性的实现。 C#中的隐式接口实现可以减轻必须定义三个属性的负担,因为定义T this[int index] {get {...}; set {...};}的类将使编译器使用指定的getter实现只读方法,使用指定的get-only方法setter,以及使用两者的读写方法,但从Framework的角度来看,实际上有三个独立的属性,读/写属性的get / set独立于与read相关的get / set属性 - 只写或只写属性。

就我个人而言,我认为vb.net和C#都不愿意使用一个属性被读取的事实作为一个线索,即在重载解析中不应该考虑只写属性,这也很烦人。它的编写应该排除只读属性,但我没有设计这些语言,也没有设计框架。