Question Appel de surcharge du constructeur lorsque les deux surcharges ont la même signature


Considérons la classe suivante,

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

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

Le code ci-dessus n'est pas valide et ne sera pas compilé. Considérons maintenant le code suivant,

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

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

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

Le code ci-dessus est valide et compile bien. Il appelle Foo (int count).

Ma question est la suivante: si le premier est invalide, comment le second peut-il être valide? Je connais la classe Foo <t> est valide car T et int sont des types différents. Mais quand il est utilisé comme Foo <int> foo = new Foo <int> (1), T obtient un type entier et les deux constructeurs auront la même signature, non? Pourquoi le compilateur ne montre-t-il pas d'erreur plutôt que de choisir une surcharge à exécuter?


18
2017-08-18 11:04


origine


Réponses:


Votre question a été vivement débattue lors de la conception de C # 2.0 et du système de type générique du CLR. Tellement chaud, en fait, que la spécification "liée" C # 2.0 publiée par A-W a en fait la mauvaise règle! Il y a quatre possibilités:

1) Rendre illégal de déclarer une classe générique qui pourrait POSSIBEMENT être ambiguë sous la construction SOME. (C'est ce que la spécification liée dit à tort est la règle). Foo<T> déclaration serait illégale.

2) Rendre illégal la construction d'une classe générique d'une manière qui crée une ambiguïté. déclarer Foo<T> serait légal, en construisant Foo<double> serait légal, mais construire Foo<int> serait illégal.

3) Assurez-vous que tout est légal et utilisez des astuces de résolution de surcharge pour déterminer si la version générique ou non générique est meilleure. (C'est ce que fait C #)

4) Faites autre chose à laquelle je n’ai pas pensé.

La règle n ° 1 est une mauvaise idée car elle rend impossible certains scénarios très courants et inoffensifs. Prenons par exemple:

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
}

Vous voulez que ce soit illégal simplement parce que C<Stream> c'est ambigu? Beurk. La règle n ° 1 est une mauvaise idée, alors nous l'avons supprimée.

Malheureusement, ce n'est pas aussi simple que cela. Selon les règles de la CLI de l'IIRC, une implémentation est autorisée à être rejetée en tant que constructions illégales qui provoquent effectivement des ambiguïtés de signature. C'est-à-dire que les règles CLI ressemblent à la règle n ° 2, alors que C # implémente la règle n ° 3. Ce qui signifie qu’il pourrait en théorie y avoir des programmes C # légaux qui se traduisent par un code illégal, ce qui est extrêmement regrettable.

Pour en savoir plus sur la façon dont ces ambiguïtés rendent nos vies misérables, voici quelques articles que j'ai écrits sur le sujet:

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


19
2017-08-18 16:32



Il n'y a pas d'ambiguïté, car le compilateur choisira la surcharge la plus spécifique de Foo(...) qui correspond à. Étant donné qu'une méthode avec un paramètre de type générique est considérée comme moins spécifique qu'une méthode non générique correspondante, Foo(T) est donc moins spécifique que Foo(int) quand T == int. En conséquence, vous invoquez le Foo(int) surcharge.

Votre premier cas (avec deux Foo(int) definitions) est une erreur car le compilateur n'autorisera qu'une seule définition de méthode avec exactement la même signature, et vous en avez deux.


23
2017-08-18 11:10



Eric Lippert blogué à propos de cela récemment.


9
2017-08-18 11:14



Le fait est qu'ils n'ont pas tous les deux la même signature - l'un utilise des génériques alors que l'autre ne le fait pas.

Avec ces méthodes en place, vous pouvez aussi l'appeler en utilisant un objet non-int:

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

0
2017-08-18 11:14