Why is Linq.Sum() with decimals ambiguous between int and int?

2.7k views Asked by At

short:
I have a method decimal GetAmount(IThing thing). Using things.Sum(GetAmount) results in a CS0121 error: call is ambiguous between Sum(Func<T, int>) and Sum(Func<T, int?>). Why?

longer:

public interface IThing { }
public class Sample {
    public decimal GetAmount(IThing thing) { return 0; }
    public decimal Total(IThing[] things) { return things.Sum(GetAmount); }
}

Error CS0121 The call is ambiguous between the following methods or properties: 'Enumerable.Sum<TSource>(IEnumerable<TSource>, Func<TSource, int>)' and 'Enumerable.Sum<TSource>(IEnumerable<TSource>, Func<TSource, int?>)'

I would somewhat understand if the compiler was confused between decimal and decimal? or if the compiler couldn't pick any of the many Sum overloads. But why/how did it limit itself to only int and int?

Btw, VS / R# 'helpfully' highlights passing GetAmount to .Sum() as an error and suggests passing an int returning method instead. The compiler doesn't flag this part as a second error. Changing GetAmount to int GetAmount(IThing thing) actually does 'solve' the compiler error.

ps. I'm not looking for a solution to the compiler error. I know I can turn GetAmount into Func<IThing, decimal> GetAmount { get; } or implement Total like things.Sum(thing => GetAmount(thing)). And as suggested by @IvanStoev, things.Sum(new Func<IThing, decimal>(GetAmount)) works (for me) as well.

2

There are 2 answers

7
teo van kot On

You just don't have overload of .Sum() where you can pass your method.

You right you can do it that way:

things.Sum(thing => GetAmount(thing))

thing => GetAmount(thing) - this part basically will create anonymouse function and .Sum() got overload for it.

One of the other ways to implement it (more obviouse way so you can understand what actually happends) is create func yourself:

public decimal Total(IThing[] things) 
{ 
   return things.Sum(new Func<IThing, decimal>(GetAmount)); 
}

Actually i get another compiler error with your code (I use VS 2015).

Severity Code Description Project File Line Suppression State Error CS0407 'decimal Sample.GetAmount(IThing)' has the wrong return type

So I think you get that wired error only becouse precompiler analizer is not perfect.

I did a bit more research and try to compile your code without precompiler from command prompt like this:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /t:exe /out:Program.exe Program.cs

And now compiler return right error:

Program.cs(13,56): error CS0121: The call is ambiguous between the following methods or properties: 'System.Linq.Enumerable.Sum<Lambda.IThing>(System.Collections.Generic.IEnumerable<Lambda.IThing>, System.Func<Lambda.IThing,decimal>)' and 'System.Linq.Enumerable.Sum<Lambda.IThing>(System.Collections.Generic.IEnumerable<Lambda.IThing>, System.Func<Lambda.IThing,decimal?>)'

As you see now we got right error with decimal type. So wierd compiler error that we get located somewhere in precompiler sources.

0
Andy Lamb On

The error message associated with CS0121 only displays the first two ambiguous matches. The error applies to all signatures of Sum. You can prove this yourself by writing an extension class that matches the Sum signature and switching the order of the methods within the class:

public static class Extensions
{
    public static decimal TestSum<T>(this IEnumerable<T> source, Func<T, decimal> selector)
    {
        return 0;
    }

    public static int TestSum<T>(this IEnumerable<T> source, Func<T, int> selector)
    {
        return 0;
    }

    public static int? TestSum<T>(this IEnumerable<T> source, Func<T, int?> selector)
    {
        return 0;
    }
}

CS0121  The call is ambiguous between the following methods or properties: 
'Extensions.TestSum<T>(IEnumerable<T>, Func<T, decimal>)' and 
'Extensions.TestSum<T>(IEnumerable<T>, Func<T, int>)'

The return type of a method is not considered to be part of it's signature. Again you can prove this by writing two methods with the same name that only differ in return type:

public class TestClass
{
    public decimal TestReturn(int value) { return 0m; }
    public int TestReturn(int value) { return 0; }
}

CS0111  Type 'TestClass' already defines a member called 'TestReturn' with the same parameter types

So when looking for an overload of Sum that accepts a Func with the correct signature, only parameters are considered, not return types, in this case IThing, which causes it to match all overloads of Sum and therefore be ambiguous.