Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected effect of implicit cast on delegate type inference

I have a simple Money type with an implicit cast from decimal:

struct Money
{
    decimal innerValue;
    public static implicit operator Money(decimal value)
    {
        return new Money { innerValue = value };
    }
    public static explicit operator decimal(Money value)
    {
        return value.innerValue;
    }

    public static Money Parse(string s)
    {
        return decimal.Parse(s);
    }
}

And I defined a Sum() overload to operate on those values:

static class MoneyExtensions
{
    public static Money Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, Money> selector)
    {
        return source.Select(x => (decimal)selector(x)).Sum();
    }
}

What I didn't expect was for this extension method to interfere with the existing Sum() extension methods:

var source = new[] { "2" };
Money thisWorks = source.Sum(x => Money.Parse(x));
int thisWorksToo = source.Sum(new Func<string, int>(x => int.Parse(x)));
int thisDoesNot = source.Sum(x => int.Parse(x));

The error is "Cannot implicitly convert type 'Money' to 'int'. An explicit conversion exists (are you missing a cast?)". Is it correct that the compiler favors int => decimal => Money implicit conversions over resolving an overload that's an exact match?

like image 361
dahlbyk Avatar asked Jun 30 '11 12:06

dahlbyk


2 Answers

From the C# 4.0 Specification, section 7.6.5.2:

The preceding rules mean that instance methods take precedence over extension methods, that extension methods available in inner namespace declarations take precedence over extension methods available in outer namespace declarations, and that extension methods declared directly in a namespace take precedence over extension methods imported into that same namespace with a using namespace directive

Probably, this is causing your Money Sum extension method to take precedence over the ones from Linq - that's why you don't get an "ambiguous method call" error.

like image 197
RobSiklos Avatar answered Nov 07 '22 07:11

RobSiklos


Following on from Rob Siklos's research, (please vote up the research) Putting the extension in a seperate namespace fixes this problem. I seem to recall this as one of the guidelines for extensions.

using System;
using System.Collections.Generic;
using System.Linq;
using Extensions;

namespace Currency
{
    struct Money
    {          
        decimal innerValue;
        public static implicit operator Money(decimal value)
        {
            return new Money { innerValue = value };
        }
        public static explicit operator decimal(Money value)
        {
            return value.innerValue;
        }
        public static Money Parse(string s)
        {
        return decimal.Parse(s);
        }
     }

     class Program
     {
         static void Main()
         {
             var source = new[] { "2" };
             Money thisWorks = source.Sum(x => Money.Parse(x));
             int thisWorksToo = 
                 source.Sum(new Func<string, int>(x => int.Parse(x)));       
             int thisWorksTooNow = source.Sum(x => int.Parse(x));

         }
     }
}
namespace Extensions
{
    static class IEnumerableTExtensions
    {
        public static Currency.Money Sum<TSource>(
                                       this IEnumerable<TSource> source,
                                       Func<TSource, Currency.Money> selector)
        {
            return source.Select(x => (decimal)selector(x)).Sum();
        }
    }
}
like image 38
Jodrell Avatar answered Nov 07 '22 09:11

Jodrell