Неожиданный эффект неявного приведения на вывод типа делегата - PullRequest
9 голосов
/ 30 июня 2011

У меня есть простой тип 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));

Ошибка: «Невозможно неявно преобразовать тип« Деньги »в« int ».существует явное преобразование (вам не хватает приведения?) ".Правильно ли, что компилятор предпочитает неявные преобразования int => decimal => Money вместо разрешения перегрузки, которая точно соответствует?

Ответы [ 3 ]

8 голосов
/ 30 июня 2011

Из спецификации C # 4.0, раздел 7.6.5.2:

Предыдущие правила означают, что методы экземпляра имеют приоритет над методами расширения, что методы расширения, доступные в объявлениях внутреннего пространства имен, имеют приоритет над методами расширения, доступными в объявлениях внешнего пространства имен, и что методы расширения, объявленные непосредственно в пространстве имен, имеют приоритет над импортированными методами расширения. в это же пространство имен с помощью директивы use namespace

Вероятно, это приводит к тому, что ваш метод расширения Money Sum имеет приоритет над методами из Linq - поэтому вы не получаете ошибку "неоднозначный вызов метода".

2 голосов
/ 30 июня 2011

Исходя из исследования Роба Сиклоса, (пожалуйста, оцените исследование) Помещение расширения в отдельное пространство имен устраняет эту проблему. Кажется, я вспоминаю это как одно из руководств для расширений.

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();
        }
    }
}
0 голосов
/ 30 июня 2011

Это потому, что вы явно объявляете 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());
}

Из спецификации :

Группа методов определяет один метод для вызова или наборперегруженных методов, из которых можно выбрать конкретный метод для вызова. В последнем случае определение конкретного метода для вызова основывается на контексте, предоставленном типами аргументов в списке аргументов .

...