简体   繁体   中英

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?

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.

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();
        }
    }
}

It's because you are explicitly declaring thisDoesNot as type int . If you use implicit declaration, it works fine:

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)));
    var thisDoesNot = source.Sum(x => int.Parse(x));

    Console.Write(thisDoesNot.GetType());
}

From the specification :

The method group identifies the one method to invoke or the set of overloaded methods from which to choose a specific method to invoke. In the latter case, determination of the specific method to invoke is based on the context provided by the types of the arguments in the argument-list .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM