Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is a property get considered ambiguous, when the other interface is set-only?

In the following code:

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'
    }
}

I get an error:

Ambiguity between 'IGet.Value' and 'ISet.Value'

Why can't the compiler determine that the property accessed must be from IGet?

Edit - I'm guessing the compiler first tries to determine which property is being accessed, and only then checks whether it's a get or a set. The question is - why? Why not rule out those candidates that don't provide a getter? Why not resolve the actual get/set methods and ignore the fact it's a property?

Update: Some more code using methods instead of properties, where the problem doesn't exist. Properties are not exactly like a pair of methods.

like image 310
sinelaw Avatar asked Dec 05 '13 23:12

sinelaw


2 Answers

I'm guessing the compiler first tries to determine which property is being accessed, and only then checks whether it's a get or a set.

Your guess is correct.

The question is - why? Why not rule out those candidates that don't provide a getter? Why not resolve the actual get/set methods and ignore the fact it's a property?

The compiler could resolve all property candidates and then rule out the ones that don't provide get - why doesn't it do that? – sinelaw

Because the language designers didn't design it that way? – Robert Harvey

@sinelaw Because that's not how the C# language is defined. It isn't that it couldn't be done, it is simply that it's not done. – user2864740

I'm sure they designed it that way (unlikely they overlooked this situation) - but what's the reasoning? – sinelaw

@sinelaw Presumably because they didn't feel the benefit from such a feature would out way the added complexity in developing it. – p.s.w.g

pswg is on the right track here, but we can be more specific.

The basic design principle here is analysis proceeds from inside to outside without considering "contextual cues". It is both confusing for the reader and difficult for the compiler and IDE developer when the meaning of an expression depends on its immediate context. What we want to do is work out the meaning of each expression unambiguously, and then verify that it works in its context. We don't want to go the other way, and say "well, this expression is ambiguous, so let me use the context as a clue".

More specifically: first the compiler must determine the meaning of a, and then a.Value, then determine whether the assignment is legal. The compiler does not say "well, I couldn't figure out which of two properties a.Value meant because it is ambiguous, but I'm going to muddle on through pretending that I did figure that out, and go back and patch things up when I realize that I'm on the value side of an assignment and only one of these things has a value". Nor does the compiler say "I'm going to use one lookup algorithm when I'm on the left side of an assignment and a different one when I'm on the right side".

(Aside: of course we are not technically speaking in an assignment here; we are in an initializer of an implicitly typed local, which is not classified as a usage of the assignment operator. But it is logically equivalent to such, so we'll let that pass without further comment.)

There are some exceptions to this general rule which are targeted towards specific common situations. The compiler does know for example that in an expression of the form a.B() that B needs to be something invokable; the member lookup algorithm automatically rejects non-invokable members without giving an error. Lambdas of course utterly reject this principle; the meaning of a lambda is entirely determined by its context. Making this work took a huge amount of work -- this was one of my features for C# 3 -- and we made a large investment to ensure that the algorithms were performant in common scenarios. Any time you reason from outside to inside and inside to outside at the same time you end up in potentially exponential situations where you must make every possible trial binding and then choose the unique one that works. This cost is worth it for a great feature like type-inferred lambdas. Making other forms of context sensitivity work, particularly for obscure scenarios as the one you describe, is not a good way to spend limited budget.

So the code example in my answer works because it "merges" the two property definitions into a single call (eliminating the compiler ambiguity) while fulfilling the two interface contracts for both the getter and the setter? Robert Harvey

To clarify, Robert's code from his deleted answer is:

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"

Robert I am not sure I understand your question. Your code works because first, the contracts for ISet and IGet are fulfilled. Class GetSet has all the members required by each and an unambiguous mapping. And second because your call site does not use the interfaces at all; it just calls the members of the class directly. Why wouldn't it work?

Now to address a point in your deleted answer:

Just having another interface that inherits the original two isn't going to work, because there's no backing field to bind to.

No, this is not a correct analysis. This has nothing to do with whether the property is actually implemented as a compiler-generated field or not. Remember, properties on interfaces are just fancy ways of defining get_Value and set_Value methods. As long as a property with the required method exists on the implementing class, the interface requirement is satisfied. How that property is implemented is up to the class.

one class property fulfills the Interface of two different contracts.

Yes! That's not a problem. As long as the mapping from interface member to class/struct member can be unambiguously determined, it's fine. For instance:

interface IFoo
{
    void M();
}

interface IBar
{
    void M();
}

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

M can do double-duty as both IFoo.M and IBar.M.

Where you get into trouble is when it cannot be easily determined which method matches the interface. See my article on that subject for details:

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

And for some interesting related shenanigans see this question and the answers, which both Lucian and I addressed:

Generic type parameter covariance and multiple interface implementations

like image 191
Eric Lippert Avatar answered Oct 22 '22 21:10

Eric Lippert


I agree with your analysis. It seems that the compiler needs to resolve the symbol a.Value before it analyzes how you're using it to convert the property getter to a call to get_Value.

It may be worth noting that if you do this:

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

You don't get a 'does not contain a definition' error. You get this:

The property or indexer 'ISet.Value' cannot be used in this context because it lacks the get accessor

The compiler found the symbol, bound it to ISet.Value, and only after that, complained about how it was used (because ISet doesn't provide the getter).

like image 7
p.s.w.g Avatar answered Oct 22 '22 20:10

p.s.w.g