隐式强制转换对委托类型推断的意外影响

时间:2011-06-30 12:15:53

标签: c# delegates type-inference

我有一个简单的Money类型,其隐式广告来自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);
    }
}

我定义了Sum()重载以对这些值进行操作:

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

我没想到的是这种扩展方法会干扰现有的Sum()扩展方法:

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

错误是“无法将类型'Money'隐式转换为'int'。存在显式转换(您是否错过了转换?)”。编译器是否正确支持int => decimal => Money隐式转换而不是解决完全匹配的重载?

3 个答案:

答案 0 :(得分:8)

从C#4.0规范,第7.6.5.2节:

  

前面的规则意味着实例方法优先于扩展方法,内部命名空间声明中可用的扩展方法优先于外部命名空间声明中可用的扩展方法,并且直接在命名空间中声明的扩展方法优先于导入的扩展方法使用using namespace directive

进入相同的命名空间

可能这会导致您的Money Sum扩展方法优先于Linq的方法 - 这就是为什么你没有得到“模糊的方法调用”错误。

答案 1 :(得分:2)

继Rob Siklos的研究之后(请将研究结果投票) 将扩展名放在单独的命名空间中可以解决此问题。我似乎记得这是扩展指南之一。

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

答案 2 :(得分:-1)

这是因为您明确声明thisDoesNotint类型。如果你使用隐式声明,它可以正常工作:

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

来自specification

  

方法组识别出一个   调用方法或集合   重载的方法   选择要调用的特定方法。的在   后一种情况,确定了   要调用的具体方法是基于   由类型提供的上下文   参数列表中的参数