24

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.

sinelaw
  • 16,205
  • 3
  • 49
  • 80
  • 5
    @p.s.w.g, they still implement two different properties, though. You cannot "share" properties that way. – Frédéric Hamidi Dec 05 '13 at 23:17
  • 3
    Because it is not up to the compiler, these are CLI rules. With an extremely non-trivial iceberg of implementation details, binding an interface to its implementation is quite a circus. Having the compiler do something freaky like merging the two interfaces into a single one just buys enormous Reflection pain and suffering. – Hans Passant Dec 05 '13 at 23:44
  • 3
    Note that even within the same class, you can't write `int Value {get {return _value;}} int Value {set {_value = value;}}`, so the C# compiler treats properties as more than the sum of their `get_` and `set_` methods. – dan04 Dec 06 '13 at 00:08
  • How would you handle `a.Value = a.Value`? – Brian Rasmussen Dec 06 '13 at 00:40
  • @BrianRasmussen, the left side would resolve to `ISet.Value`; the right side would resolve to `IGet.Value` – sinelaw Dec 06 '13 at 02:12

3 Answers3

14

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

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • So the code example in my answer works because it "merges" the two property definitions from the Interfaces into a single call in the class (eliminating the compiler ambiguity) while simultaneously fulfilling the two interface contracts for both the getter and the setter? It's novel, but for some reason I didn't quite expect it to work that way. – Robert Harvey Dec 06 '13 at 01:01
  • 1
    @RobertHarvey: I've updated my answer to address your question. – Eric Lippert Dec 06 '13 at 01:08
  • It will also work if you cast `getSet` to `ISet`, `IGet`, or even `IBoth` (you get the capabilities commensurate with whatever you cast it to). In other words, I can force it to use the Interface call sites, if I want to. – Robert Harvey Dec 06 '13 at 01:10
  • @RobertHarvey: Please post working code when casting to `IBoth`. I cannot make that work. – Jean Hominal Dec 06 '13 at 01:18
  • @JeanHominal: `var both = (IBoth)getSet;` where `getSet` is an instance of the `GetSet` class in my answer. – Robert Harvey Dec 06 '13 at 01:22
  • @RobertHarvey: OK. I should have said "working code that uses the `Value` property on the casted variable." – Jean Hominal Dec 06 '13 at 01:23
  • @JeanHominal: Ah, I see what you mean. You're right; that didn't work. Had a small bug in my code. It only works if you cast `ISet` or `IGet`. – Robert Harvey Dec 06 '13 at 01:25
  • @RobertHarvey: I've updated my answer to address your comment. – Eric Lippert Dec 06 '13 at 01:26
  • @EricLippert, thanks for the answer and especially for diving in on the rationale. – sinelaw Dec 06 '13 at 02:00
  • @EricLippert, I guess my expectation was that binding resolution would do "magic" like [type-inference for lambda calculus](http://en.wikipedia.org/wiki/Type_inference#Hindley.E2.80.93Milner_type_inference_algorithm) does. I wonder if a compiler could be built using that approach - e.g. methods/values/etc. as types, the code being compiled as an expression in a DSL based on those types, and the compiler binding resolution being the process of inferring the "type" (target binding) for each sub-expression in the code... – sinelaw Dec 06 '13 at 02:07
  • @sinelaw: F# is the language for you. – Eric Lippert Dec 06 '13 at 06:37
  • @sinelaw: To clarify my last comment: F# is a functional language provided by Microsoft which uses HM type inference throughout. The F# compiler is written in F# but I do not know what type inference tricks it uses to itself do type inference on source code. You'd have to ask an expert on F#. – Eric Lippert Dec 06 '13 at 17:24
  • @EricLippert, Thanks - will do. My problem is how to push F# into my work environment... – sinelaw Dec 06 '13 at 17:35
  • @sinelaw Start using it for yourself (I now use F# for most small console apps / utilities I need to write). Once you become proficient enough you can become an advocate and encourage other developers to start learning it as well. If you are able to demonstrate a significant stability or productivity increase in switching to F#, you might be able to convince your boss(es) to let use that as a primary language for production projects. – p.s.w.g Dec 06 '13 at 19:03
7

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).

p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • 2
    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 Dec 05 '13 at 23:28
  • 2
    Because the language designers didn't design it that way? – Robert Harvey Dec 05 '13 at 23:31
  • 3
    @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 Dec 05 '13 at 23:31
  • I'm sure they designed it that way (unlikely they overlooked this situation) - but what's the reasoning? – sinelaw Dec 05 '13 at 23:35
  • 2
    @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 Dec 05 '13 at 23:40
  • Because if it was designed that way, we would get the ambiguity elsewhere. – Ken Kin Dec 06 '13 at 17:20
5

The creators of .NET have decided that a property should be a special kind of entity in its own right, rather than merely allowing an alternate form of invocation for suitably-named methods with a "property" attribute. This is at times annoying; the one potentially-useful aspect is that it ensures that prevents the possibility of code overriding a property getter without realizing that the property also has a setter, or overriding a property setter without realizing that the getter does something other than returning the expected backing field.

This, if one wished to have a covariant set of list interfaces, one would need to define:

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

It would in turn be necessary for code to define implementations for all three properties. Implicit interface implementation in C# may ease the burden of having to define three properties, since a class which defines T this[int index] {get {...}; set {...};} will cause the compiler to implement the read-only method using the specified getter, the write-only method using the specified setter, and the read-write method using both, but from the perspective of the Framework, there are in fact three separate properties, and the get/set of the read/write property is independent of the get/set properties associated with the read-only or write-only properties.

Personally, I think it's annoying that neither vb.net nor C# is willing to use the fact that a property is being read as a clue that write-only properties shouldn't be considered in overload resolution, and likewise the fact that it's being written should exclude read-only properties, but I didn't design those languages nor the Framework.

supercat
  • 77,689
  • 9
  • 166
  • 211
  • 1
    What would that look like in the OP's example? Can they just shadow the `Value` property in the composite Interface, like you are doing here? Is there a point to doing that? – Robert Harvey Dec 06 '13 at 00:50
  • @RobertHarvey you can, in fact my code (where I stumbled into this issue) now uses `new`. Everything works as expected until you define a class `Get: IGet` and then derive from `Both: Get, IBoth` - you'll have strange behavior when casting `Both` to `Get` (the property getter will not be virtualized) – sinelaw Dec 06 '13 at 02:11
  • @sinelaw: That brings up a point I should perhaps have mentioned: because of all the quirks associated with properties, it's often a good idea for an interface property implementation to do nothing more than chain to protected virtual get and/or put methods [the read-only and read-write interfaces should use the same "get" method; likewise write-only, read-write, and "set"]. Otherwise it's hard to ensure that the read-write "get" and read-only "get" will behave consistently. – supercat Dec 06 '13 at 20:54
  • @KenKin: Thanks for the syntax fix. I prefer on posts to avoid using too much vertical whitespace, so I tightened things up some compared with your edit. Actually, even in real life I prefer interface members to be one line each; using three lines to indicate a one-of-three choice (read-only, write-only, or read-write) seems rather excessive. – supercat Dec 06 '13 at 21:23
  • @supercat: Oh, sorry. I'm not meant to increase the vertical length, just for fixing some semicolon and the curly braces .. but I have a bad habit of [Ctrl+K, D] then [Ctrl+S], and the editor formats and saves it .. – Ken Kin Dec 06 '13 at 21:29