2009-08-18 10 views
18

Considérese la siguiente clase,Calling sobrecarga de constructor cuando ambos tienen una sobrecarga misma firma

class Foo 
{ 
    public Foo(int count) 
    { 
     /* .. */ 
    } 

    public Foo(int count) 
    { 
     /* .. */ 
    } 
} 

Por encima de código no es válido y no se compilará. Ahora considere el siguiente código,

class Foo<T> 
{ 
    public Foo(int count) 
    { 
     /* .. */ 
    } 

    public Foo(T t) 
    { 
     /* .. */ 
    } 
} 

static void Main(string[] args) 
{ 
    Foo<int> foo = new Foo<int>(1); 
} 

El código anterior es válido y compila bien. Llama a Foo (recuento int).

Mi pregunta es, si la primera no es válida, ¿cómo puede la segunda ser válida? Sé que la clase Foo <T> es válida porque T e int son tipos diferentes. Pero cuando se usa como Foo <int> foo = new Foo <int> (1), T está obteniendo el tipo de enteros y ambos constructores tendrán la misma firma ¿no? ¿Por qué el compilador no muestra el error en lugar de elegir una sobrecarga para ejecutar?

Respuesta

19

Su pregunta se discute caliente cuando se está diseñando C# 2.0 y el sistema de tipo genérico en el CLR. ¡Tan ardientemente, de hecho, que la especificación "enlazada" C# 2.0 publicada por A-W en realidad tiene una regla incorrecta! Hay cuatro posibilidades:

1) Declarar ilegal una clase genérica que POSIBLEMENTE podría ser ambigua bajo SOME construction. (Esto es lo que la especificación encuadernada dice incorrectamente es la regla). Por lo tanto, su declaración Foo<T> sería ilegal.

2) Establezca la ilegalidad de construir una clase genérica de manera que cree una ambigüedad. declarar Foo<T> sería legal, la construcción de Foo<double> sería legal, pero la construcción de Foo<int> sería ilegal.

3) Haga que todo sea legal y use trucos de resolución de sobrecarga para determinar si la versión genérica o no genérica es mejor. (Esto es lo que realmente hace C#.)

4) Haz otra cosa en la que no haya pensado.

La regla n. ° 1 es una mala idea porque hace que algunos escenarios muy comunes e inofensivos sean imposibles.Consideremos por ejemplo:

class C<T> 
{ 
    public C(T t) { ... } // construct a C that wraps a T 
    public C(Stream state) { ... } // construct a C based on some serialized state from disk 
} 

¿Quieres que para ser ilegal sólo porque C<Stream> es ambiguo? Yuck. La regla n. ° 1 es una mala idea, así que la descartamos.

Desafortunadamente, no es tan simple como eso. IIRC Las reglas de la CLI dicen que una implementación puede rechazar como construcciones ilegales que en realidad causan ambigüedades de firma. Es decir, las reglas CLI son algo así como la Regla # 2, mientras que C# realmente implementa la Regla # 3. Lo que significa que, en teoría, podría haber programas legales de C# que se traducen en código ilegal, lo que es profundamente desafortunado.

Para algunos pensamientos más sobre cómo este tipo de ambigüedades hacen la vida miserable, aquí hay un par de artículos que escribí sobre el tema:

http://blogs.msdn.com/ericlippert/archive/2006/04/05/569085.aspx

http://blogs.msdn.com/ericlippert/archive/2006/04/06/odious-ambiguous-overloads-part-two.aspx

+0

Supongo que el tercer Foo <> en 2 tiene que ser int – Dykam

+0

"programas legales de C# que se traducen en código ilegal" ¿Cómo es posible? Pensé que la traducción da como resultado una instrucción 'newobj' y un token de método, donde' Foo .ctor (int) 'y' Foo .ctor (T) 'tienen tokens de método diferentes. Entonces no hay ambigüedad en el IL. ¿Me he perdido algo? –

+1

@BenVoigt: Primero, en lugar de "código ilegal" hubiera sido más preciso si hubiera dicho "código no verificable" o "código con comportamiento definido por la implementación". Las consecuencias de una construcción genérica que produce una colisión de firma son sutiles. Supongamos, por ejemplo, que tenemos dos métodos con la misma firma y deseamos indicar en los metadatos que uno de ellos es la implementación de un método de interfaz particular. No hay una construcción de metadatos que pueda ir en la tabla de implementación de métodos que desambigua entre dos métodos con la misma firma. Hay otros casos impares similares. –

23

No hay ambigüedad, porque el compilador elegirá la sobrecarga más específica de Foo(...) que coincida. Como un método con un parámetro de tipo genérico se considera menos específico que un método no genérico correspondiente, Foo(T) es por lo tanto menos específico que Foo(int) cuando T == int. En consecuencia, está invocando la sobrecarga Foo(int).

Su primer caso (con dos definiciones Foo(int)) es un error porque el compilador solo permitirá una definición de un método con exactamente la misma firma, y ​​usted tiene dos.

+0

he comprobado la especificación del lenguaje, pero no dice que los parámetros genéricos son menos específicos que los tipos concretos. Sin embargo, los métodos no genéricos se consideran "mejores" que los genéricos. – Thorarin

+0

Tienes razón, eso no fue redactado muy bien. Quise decir "un método con un parámetro de tipo genérico ...". –

+0

Por cierto, para los curiosos, creo que la sección de la que habla Thorarin es §14.4.2.2, que define "mejor miembro de la función". –

9

Eric Lippert blogged sobre esto recientemente.

+1

Y, aparentemente, también ha respondido la pregunta aquí. –

0

El hecho es que no tienen la misma firma, una está usando genéricos mientras que la otra no.

Con esos métodos en su lugar también se podría llamar usando un objeto no int:

Foo<string> foo = new Foo<string>("Hello World");