5

In the code:

Interface ISelf(Of Out TMe)
End Interface
Class SomeBase
    Implements ISelf(Of SomeBase)
End Class
Class SomeDerived
    Inherits SomeBase
    Implements ISelf(Of SomeDerived)
End Class
Module ISelfTester
    Sub TestISelf()
        Dim z7 As New SomeDerived
        Dim z8 As ISelf(Of SomeDerived)
        Dim z9 As ISelf(Of ISelf(Of SomeDerived))
        z8 = z7
        z9 = z8
        z9 = z7 ' Why is this illegal?
    End Sub
End Module

The assignment directly from Z7 to Z9 yields the message "Error 13 Option Strict On does not allow implicit conversions from 'wokka.SomeDerived' to 'wokka.ISelf(Of wokka.ISelf(Of wokka.SomeDerived))' because the conversion is ambiguous." How is that assignment any more ambiguous than the one from Z7 to Z8, or Z8 to Z9? So far as I can tell, all three assignments must be representation-preserving conversions, meaning that all three must simply store a reference to the same object as Z7.

I could understand that if I were trying to assign an instance of SomeDerived to a reference of type ISelf(Of ISelf(Of SomeBase)), attempting to access a member of that interface could yield the implementation from either SomeBase or SomeDerived; if the member was a method with return type TMe, I could understand that such ambiguity could cause compilation to fail (since the compiler wouldn't know what the return type would be). I'm puzzled, though, as to why merely trying to assign a reference fails because of "ambiguity", given that the assignment can't possibly be interpreted as anything other than a direct store of a reference to a reference-type variable?

BTW, the intended usage would be for ISelf(Of T) to contain a read-only property Self of type T, for which the expected implementation would be Return This [a representation-preserving conversion in every case; I suppose I should have added a class constraint to TMe, but it doesn't affect the original problem]. If has a variety of classes which one is interested in implement ISelf(Of theirOwnTypes), it should be possible to leverage the covariance of ISelf to facilitate some things that would otherwise be difficult [e.g. if each class one is interested in that implements IMoe, ILarry, and/or ICurly, etc. also implements the corresponding classes ISelfAndMoe(Of ItsOwnType), ISelfAndLarry(Of ItsOwnType), and/or ISelfAndCurly(Of ItsOwnType), etc. then one can accept a parameter type which is known to implement any combination of those interfaces e.g.ISelfAndMoe(Of ISelfAndLarry(Of ICurly)) param. Given that declaration,paramwould implementIMoe, andparam.Selfwould implementILarry, andparam.Self.Selfwould implementICurly. Further, if the class implements the expected pattern, one could castparamto e.g.ISelfAndCurly(Of IMoe), if one wanted to call a routine which needed those two interfaces but didn't needILarry` (such cast could fail if an implementation did something unexpected, but should succeed if the object's class follows the expected pattern).

supercat
  • 77,689
  • 9
  • 166
  • 211

2 Answers2

4

Option Strict On tells the compiler to ignore a simple reference to reference implicit conversion. It's implicit because z9 is allowed to be a SomeBase(Of SomeBase(Of SomeDerived) and because SomeDerived can be substituted for SomeBase the compiler isn't sure which one you mean. But because you explicitly say in z8 that it's ISelf(of SomeDerived) there isn't any guesswork. I suspect if you changed z9 to ISelf(Of SomeDerived(Of SomeDerived) that ambiguity may go away.

As an opinion, however, this sort of nesting gets very confusing as it stacks on itself and can become a nightmare to maintain.

Joel Etherton
  • 37,325
  • 10
  • 89
  • 104
  • Z9 is declared as `ISelf(Of ISelf(Of SomeDerived))`; I'm not sure what else it could be (`SomeBase` is not a generic type, so I think you meant something else). – supercat Dec 10 '12 at 18:34
  • @supercat: No, I said what I meant, I don't think you saw it from the angle I was looking at it though. From the compiler's viewpoint, z9 is allowed to be either `SomeBase(Of ISelf(Of SomeDerived))` or `SomeDerived(Of ISelf(Of SomeDerived))`. The second usage of the interface allows this ambiguity to creep in, and even though the base type is `SomeDerived`, there is no way for the compiler to know logically since they are substitutable as `ISelf` implementers. – Joel Etherton Dec 10 '12 at 18:39
  • The classes `SomeBase` and `SomeDerived` do not have any generic type parameters, so I'm not clear what `SomeBase(Of ...)` means? I'm not sure why the compiler should care, *when performing the assignment*, what the exact value-preserving-assignment-compatibility path of `ISelf(Of ISelf(Of SomeDerived))` is; at least one such path exists, and the semantic effect of an assignment of a class reference to an interface reference cannot depend upon the derivation path. – supercat Dec 10 '12 at 18:52
  • As noted, if `ISelf(Of T)` had a member `Self` implemented as described in my post edit above, the type of `Z9` were `ISelf(Of ISelf(Of SomeBase))`, and I were to to store an reference to a `SomeDerived` to `Z9`, then `Z9.Self` might bind to a property that returns `SomeBase` or to one that returns `SomeDerived`; that ambiguity would exist regardless of whether the reference was stored directly or went through intermediate conversions. Further, in the common scenarios where such ambiguities might occur, it would be very typical for all possible bindings to return the same object. – supercat Dec 10 '12 at 19:05
0

I also find this hard to understand. Had it been instead:

Dim y9 As ISelf(Of ISelf(Of SomeBase))
y9 = z7

it would be more easy to imagine many "routes" from SomeDerived to ISelf(Of ISelf(Of SomeBase)).

Looking at the example of the question, clearly:

SomeDerived "is a" ISelf(Of SomeDerived)

by declaration. From here, using covariance, one gets to:

ISelf(Of SomeDerived) "is a" ISelf(Of
                                      ISelf(Of SomeDerived)
                                                           )

But are there more than one way to get from the first "equation" to this one? One way seems to be to insert the first equation into itself, using covariance of course. Another way might be to apple ISelf(Of ...) on both sides of the "is a" in the first equation, and then use some kind of transitivity to combine the original equation with the result.

I'm really not sure if those are distinct ways. Is there some kind of associativity here, as in the abstract (formal) mathematical equation:

(a·a)*x  =  a*(a*x)

Maybe I don't make sense, and this is just an error in the VB.NET compiler. What happens if you delete the base class SomeBase from the example? Does this behavior still occur?

There's another question on errors with contravariance where it's claimed that VB.NET is overly strict. But maybe it's better to be too strict than too loose like C# seems to be.

Community
  • 1
  • 1
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • Looking at the first question you linked, that scenario involves specific disjoint types which cannot possibly resolve to the same one (but the compiler doesn't realize that); the second relates to a situation which is closer to this one, though with ambiguity resulting from contravariance rather than covariance. I would suggest that there are so many cases where ambiguity will exist between what would typically be substantially-identical implementations that forbidding ambiguity would weaken the language compared with making allowing the run-time to arbitrarily select among alternatives. – supercat Dec 12 '12 at 17:27