niceman
niceman

Reputation: 2673

strange compiler error with C# mono

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

Answers (2)

user743382
user743382

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.
  • therefore, Func<int?> is a better conversion target than Func<decimal?>.
  • therefore, a conversion from () => 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

Steve
Steve

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

Related Questions