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();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With