繁体   English   中英

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

[英]Unexpected effect of implicit cast on delegate 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隐式转换而不是解决完全匹配的重载是否正确?

来自 C# 4.0 规范,第 7.6.5.2 节:

前面的规则意味着实例方法优先于扩展方法,内部命名空间声明中可用的扩展方法优先于外部命名空间声明中可用的扩展方法,并且直接在命名空间中声明的扩展方法优先于导入到同一命名空间中的扩展方法带有 using 命名空间指令的命名空间

可能这会导致您的 Money Sum 扩展方法优先于 Linq 中的扩展方法 - 这就是为什么您不会收到“模糊方法调用”错误的原因。

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

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

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

规范

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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