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.