[CSHARP-280] Compiler error: ambiguous call between IEnumerable<MongoDB.Bson.BsonValue> and IEnumerable<object> in BsonArray Created: 25/Jul/11  Updated: 02/Apr/15  Resolved: 27/Jul/11

Status: Closed
Project: C# Driver
Component/s: None
Affects Version/s: 1.1
Fix Version/s: 1.2

Type: Bug Priority: Major - P3
Reporter: Roman Kuzmin Assignee: Robert Stam
Resolution: Done Votes: 0
Labels: driver
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Backwards Compatibility: Major Change

 Description   

*) Build environment:
VS 2010, Target platform: .NET 3.5, driver versions: 1.1 both official and the latest sources

*) Steps to reproduce:
Add the following method to the MongoDB.BsonUnitTests.BsonArrayTests:

void TestCompilerError()
{
var values = new BsonValue[] { };

// all 3 calls cannot compile
BsonArray.Create(values);
var array = new BsonArray(values);
array.AddRange(values);
}

.. and compile. Compilation fails with an error (3 similar errors):

The call is ambiguous between the following methods or properties: 'MongoDB.Bson.BsonArray.Create(System.Collections.Generic.IEnumerable<MongoDB.Bson.BsonValue>)' and 'MongoDB.Bson.BsonArray.Create(System.Collections.Generic.IEnumerable<object>)'

*) Possible solution:
Remove 3 methods dealing with System.Collections.Generic.IEnumerable<object> from MongoDB.Bson.BsonArray (constructor, Create, AddRange). As it was mentioned in https://jira.mongodb.org/browse/CSHARP-276, these methods are redundant after adding methods dealing with non-generic IEnumerable. Now they seem to be also troublesome.

I tried to remove these 3 methods from the latest sources. The test code is compiled successfully.



 Comments   
Comment by Robert Stam [ 27/Jul/11 ]

Rarely backward breaking at the source code level, but client code will have to be recompiled to pick up the new matching overloads in some cases.

Note: there appear to be some similar ambiguities between IDictionary and IDictionary<string, object> but I will leave those for a separate JIRA ticket.

Comment by Roman Kuzmin [ 27/Jul/11 ]

I am glad that you come to the same conclusion about replacing IEnumerable<object> in parameters with non-generic IEnumerable. As for the return values, yes, I prefer IEnumerable<object>, too, and this does not make any issues (as far as I know and can tell, neither in C# nor in PowerShell).

Comment by Robert Stam [ 27/Jul/11 ]

The reason the call is ambiguous when .NET 3.5 is targeted is that in the absence of covariance there is no implicit conversion between IEnumerable<BsonValue> and IEnumerable<object>, so to the compiler both overloads are equally applicable and therefore ambiguous.

In .NET 4 with the introduction of covariance there IS an implicit conversion from IEnumerable<BsonValue> to IEnumerable<object> but not the other way around, and that is enough to make the compiler decide that the overload with the more specific parameter type (i.e. IEnumerable<BsonValue>) is better and therefore when .NET 4 is targeted there no longer is any ambiguity.

Furthermore, I agree with your analysis that wherever IEnumerable<object> is used (at least as an input parameter) we can use IEnumerable instead. I think for return values we can still stick to IEnumerable<object> where appropriate.

Comment by Roman Kuzmin [ 25/Jul/11 ]

I propose the following (just like in https://jira.mongodb.org/browse/CSHARP-276). Replace API methods dealing with IEnumerable<object> with non-generic methods dealing with IEnumerable (perhaps not only in BsonArray). Reasons:

1) As we can see, non-generic methods open doors for PowerShell code;
2) Technically we lose nothing by this replacement, all methods that used to call X(IEnumerable<object>) can now call X(IEnumerable), at least after rebuild;
3) We can see that pairs X(IEnumerable) + X(IEnumerable<T>) in 3.5 are not ambiguous (unlike X(IEnumerable<object>) + X(IEnumerable<T>);
4) In 4.0 both ways do (old with X(IEnumerable<object> and new with non-generic IEnumerable).

I think that in 3.5 methods X(IEnumerable<object>) should be avoided. They are not different by nature than X(IEnumerable) but they cannot be called with IEnumerable references. In contrast, non-generic methods can be called with IEnumerable<object>. Thus, API with non-generics gets more cases working fine in 3.5.

As for the reasons of differences between 3.5 and 4.0., I am not sure. Compiler was changed, indeed. E.g. Covariance and Contravariance in Generics:
http://msdn.microsoft.com/en-us/library/dd799517.aspx

Comment by Robert Stam [ 25/Jul/11 ]

At first I thought this issue was introduced by the implementation of CSHARP-276, but apparently it has nothing to do with it. You can get the same compiler error with v1.1 of the driver (as long as you target .NET 3.5 instead of 4).

Here's a short sample program that illustrates the issue without using the C# driver at all:

namespace TestAmbiguousEnumerable {
public class C {
}

public static class Program {
public static void Main(string[] args)

{ var values = new C[0]; F(values); }

private static void F(IEnumerable<object> values) {
}

private static void F(IEnumerable<C> values) {
}

// commenting out this overload makes no difference
private static void F(IEnumerable values) {
}
}
}

This program compiles cleanly when targeting .NET 4 but gives the same compiler error when targeting .NET 3.5:

Error 1 The call is ambiguous between the following methods or properties: 'TestAmbiguousEnumerable.Program.F(System.Collections.Generic.IEnumerable<object>)' and 'TestAmbiguousEnumerable.Program.F(System.Collections.Generic.IEnumerable<TestAmbiguousEnumerable.C>)' c:\users\robert stam\documents\visual studio 2010\Projects\TestAmbiguousEnumerable\Program.cs 14 13 TestAmbiguousEnumerable

Only the class name has changed.

I'm going to research this simplified case first. Anyone reading this see why this works with one version of .NET and not another?

Comment by Robert Stam [ 25/Jul/11 ]

OK. I can reproduce it now. The key to reproducing is: "Target platform: .NET 3.5".

My test program was targeted at .NET 4.0, in which case it does compile and run correctly.

Comment by Robert Stam [ 25/Jul/11 ]

I am unable to reproduce. The following code compiles for me:

var values = new BsonValue[] { };

var array1 = BsonArray.Create(values);
Console.WriteLine(array1.ToJson());

var array2 = new BsonArray(values);
Console.WriteLine(array2.ToJson());

var array3 = new BsonArray();
array3.AddRange(values);
Console.WriteLine(array3.ToJson());

and produces the following output:

[]
[]
[]
Press Enter to continue

Full source at:

http://www.pastie.org/2269950

Generated at Wed Feb 07 21:36:21 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.