Reputation: 2673
I have this code :
int? sum=Enumerable.Range(0, 1000).Sum((i) =>
{
if (((i % 3) == 0) && ((i % 5) == 0))
return i;
return null;
});
This code doesn't compile, it generates this error :
error CS0266: Cannot implicitly convert type 'decimal?' to 'int?'. An explicit conversion exists (are you missing a cast?)
I changed the code to this :
int? sum=Enumerable.Range(0, 1000).Sum<int>((int i) =>
{
if (((i % 3) == 0) && ((i % 5) == 0))
return i;
return null;
});
But the compiler error remains.
I don't understand , Enumerable.Range
returns a collection of integers and it's obvious I want the Sum
that returns int? , otherwise i
should be decimal
and as I said it doesn't matter if I insert int
before i
or not.
When I make sum
a decimal?
variable it compiles.
I thought it might be a mono bug, I don't see anything like that in this bug list.
So Am I missing something or should I file a bug ?
My mono version is 4.4.1, my OS is Arch linux x64.
Upvotes: 2
Views: 132
Reputation:
Here is a stripped down test program that demonstrates the problem outside of the BCL:
using System;
using static System.Console;
static class Program {
static void Test1(Func<int?> f) { WriteLine("Test1(Func<int?>)"); }
static void Test1(Func<decimal?> f) { WriteLine("Test1(Func<decimal?>)"); }
static void Test2(Func<decimal?> f) { WriteLine("Test2(Func<decimal?>)"); }
static void Test2(Func<int?> f) { WriteLine("Test2(Func<int?>)"); }
static void Main() {
Test1(() => null);
Test2(() => null);
}
}
When compiled with Mono (4.4), this prints:
Test1(Func<decimal?>)
Test2(Func<int?>)
When compiled with Roslyn, this prints:
Test1(Func<int?>)
Test2(Func<int?>)
When going through the C# 5.0 language specification, ultimately, which overload should be picked comes down to 7.5.3.3 Better conversion from expression, but it doesn't address this case. It gives some specific scenarios in which one overload is better than another, but for it to work here, it would need to act on what it calls the inferred return type. However, null
does not have a type. There is no inferred return type. That leaves us with nothing in the language specification that describes which overload to use.
The 5.0 language specification says the call is ambiguous. Compilers should generate an error.
Unfortunately, the language specification does not describe Microsoft's compiler. This has forced Mono to implement ugly hacks that attempt to mimic Microsoft's behaviour, because refusing to compile code that does compile with Microsoft's compiler is an easy way to get people not to take Mono seriously, as you've seen in the response to this question. Those ugly hacks are not perfect and probably never will be unless the specification correctly describes the language they designed.
An unofficial C# 6.0 language specification has been made public at https://github.com/ljw1004/csharpspec/blob/gh-pages/README.md. It describes some tweaks to the overload resolution rules (look for 7.5.3.3 Better conversion from expression and 7.5.3.5 Better conversion target):
int?
is a better conversion target than decimal?
, since there is an implicit conversion from int?
to decimal?
, but not vice versa.Func<int?>
is a better conversion target than Func<decimal?>
.() => null
to Func<int?>
is better than Func<decimal?>
.The 6.0 language specification will probably say the code is valid, and Roslyn is right. But that language specification has not been finalised yet.
Since the C# 6.0 language specification hasn't been finalised yet, it's difficult for Mono to implement it accurately and it appears that it hasn't been done yet.
Whether you treat it as a workaround of a Mono bug or missing feature, or as a fix of your code to make it more compatible to C# 5.0 though, the result is the same: using default(int?)
instead of null
, as suggested by Steve too, will make it work. In C# 5.0, the way it works is by giving the anonymous function an inferred return type of int?
, resolving the ambiguity in the desired overload's favour.
Upvotes: 1
Reputation: 181
Definitely a bug, as this does not happen with the compiler shipped with VisualStudio.
A work-around for mono seems to be to return default(int?)
instead of null.
int? sum=Enumerable.Range(0, 1000).Sum((i) =>
{
if (((i % 3) == 0) && ((i % 5) == 0))
return i;
return default(int?);
});
Upvotes: 1