2011-06-30 13 views
9

Tengo un tipo simple Money con una conversión implícita de decimal:efecto inesperado de conversión implícita de tipo de delegado inferencia

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

Y definido una sobrecarga Sum() para operar en esos valores:

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

Lo que no esperaba era que este método de extensión interfiriera con los métodos de extensión Sum() existentes:

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

El error es "No se puede convertir implícitamente el tipo 'Money' en 'int'. existe una conversión explícita (¿falta un yeso?)". ¿Es cierto que el compilador favorece int => decimal => Money conversiones implícitas sobre la resolución de una sobrecarga que es una coincidencia exacta?

+0

me falta la coincidencia exacta – Jodrell

+0

El '' sobrecarga int' de Suma() 'en System.Linq. – dahlbyk

+0

¿Puede proporcionar un ejemplo completo que genere ese error de compilación? No has mostrado tus declaraciones de 'uso', que son muy importantes en esta situación. –

Respuesta

8

Desde el C# 4.0 Especificación, sección 7.6.5.2:

Las reglas anteriores significan que los métodos de instancia tienen prioridad sobre los métodos de extensión, que los métodos de extensión disponible en las declaraciones de espacios de nombres internos tienen prioridad sobre los métodos de extensión disponibles en las declaraciones externas del espacio de nombres, y los métodos de extensión declarados directamente en un espacio de nombres tienen prioridad sobre los métodos de extensión importados en ese mismo espacio de nombres con una directiva de espacio de nombres en uso

Probablemente, esto está causando que su método de extensión Money Sum tenga prioridad sobre los de Linq, por eso no obtiene un error de "llamada a método ambiguo".

+0

Entonces, ¿debería funcionar si muevo mi método 'Sum()' a un espacio de nombres diferente? – dahlbyk

+0

@dahlbyk, sí, eso funciona, lo intenté, buena respuesta, + 1 – Jodrell

+0

@dahlbyk: sí - debería funcionar si el espacio de nombres raíz de la clase de método de extensión es diferente de donde se está ejecutando el código de prueba. – RobSiklos

-1

Es porque está declarando explícitamente thisDoesNot tipo int. Si utilizar declaración implícita, que funciona bien:

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

Desde el specification:

el grupo método identifica el método para invocar o el conjunto de métodos sobrecargados a partir de los cuales eligen un método específico para invocar. En este último caso, la determinación del método específico para invocar se basa en el contexto proporcionado por los tipos de los argumentos en la lista de argumentos.

+0

Se compila, pero' thisDoesNot' tiene el tipo 'Money' en lugar de' int'.En el escenario real, tengo una cesta de artículos con 'Money Price' y' int Quantity' - no se puede compilar una 'Sum()' en 'Quantity' returning' int'. – dahlbyk

+0

Oh, pensé que querías que fuera tipo Money. Veo ... – scottm

+0

Esa especificación es para VS 2003, antes de que existieran los métodos de extensión. – RobSiklos

2

A raíz de la investigación de Rob Siklos, (por favor vote la investigación) Poner la extensión en un espacio de nombres separado corrige este problema. Me parece recordar esto como una de las directrices para las extensiones.

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