46 votes

Désambiguïser entre deux constructeurs, lorsque deux paramètres de type sont identiques

Compte tenu de

class Either<A, B> {

    public Either(A x) {}
    public Either(B x) {}
}

Comment faire pour lever l'ambiguïté entre les deux constructeurs lorsque les deux paramètres de type sont les mêmes?

Par exemple, cette ligne:

var e = new Either<string, string>("");

Échoue avec:

L'appel est ambigu entre les méthodes suivantes ou propriétés: 'Programme.Soit.Soit(Un)' et 'Programme.Soit.Soit(B)"

Je sais que si j'avais donné les paramètres des noms différents (par exemple, A a et B b au lieu de simplement en x), j'ai pu utiliser des paramètres nommés pour lever l'ambiguïté (par exemple, new Either<string, string>(a: "")). Mais je suis intéressé à savoir comment résoudre ce problème sans changer la définition de l' Either.

Edit:

Vous pouvez écrire un couple de smart constructeurs, mais je suis intéressé à savoir si l' Eithers'constructeurs peuvent être appelés directement, sans ambiguïté. (Ou si il y a d'autres "trucs" en plus de celui-ci).

static Either<A, B> Left<A, B>(A x) {
    return new Either<A, B>(x);
}

static Either<A, B> Right<A, B>(B x) {
    return new Either<A, B>(x);
}

var e1 = Left<string, string>("");
var e2 = Right<string, string>("");

49voto

Eric Lippert Points 300275

Comment faire pour lever l'ambiguïté entre les deux constructeurs lorsque les deux paramètres de type sont les mêmes?

Je vais commencer par ne pas répondre à votre question, puis terminer avec une réponse réelle qui vous permet de contourner ce problème.

Vous n'avez pas parce que vous ne devriez jamais vous vous retrouvez dans cette position en premier lieu. C'est une erreur de conception pour créer un type générique qui peut causer des signatures de membres d'être unifié dans cette manière. Ne jamais écrire une classe comme ça.

Si vous revenez en arrière et lire l'original C# 2.0, vous verrez que la conception originale était d'avoir le compilateur de détecter les types génériques dans lequel il a été en aucune façon possible pour ce genre de problème à se présenter, et de faire la déclaration de classe illégale. Ce qu'il a faites dans les spécifications publiées, même si cela était une erreur, l'équipe de conception réalisé que cette règle était trop strict en raison de scénarios tels que:

class C<T> 
{
  public C(T t) { ... }
  public C(Stream s) { ... deserialize from the stream ... }
}

Il serait bizarre de dire que cette classe est illégal parce que vous pourriez dire C<Stream> puis être incapable de lever l'ambiguïté entre les constructeurs. Au lieu de cela, une règle a été ajoutée à la résolution de surcharge qui dit que si il y a un choix entre (Stream) et (T where Stream is substituted for T) le premier gagne.

Ainsi, la règle que ce genre d'unification est illégal a été abandonné et il est désormais autorisé. Cependant c'est une très, très mauvaise idée de faire des types d'unifier, de cette manière. Le CLR qu'il gère mal dans certains cas, et il est source de confusion pour le compilateur et les développeurs. Par exemple, pourriez-vous deviner à la sortie de ce programme?

using System;
public interface I1<U> {
    void M(U i);
    void M(int i);
}

public interface I2<U> {
    void M(int i);
    void M(U i);
}

public class C3: I1<int>, I2<int> {
    void I1<int>.M(int i) {
        Console.WriteLine("c3 explicit I1 " + i);
    }
    void I2<int>.M(int i) {
        Console.WriteLine("c3 explicit I2 " + i);
    }
    public void M(int i) { 
        Console.WriteLine("c3 class " + i); 
    }
}

public class Test {
    public static void Main() {
        C3 c3 = new C3();
        I1<int> i1_c3 = c3;
        I2<int> i2_c3 = c3;
        i1_c3.M(101);
        i2_c3.M(102);
    }
}

Si vous compilez ce avec des messages d'avertissement est activée, vous verrez l'avertissement que j'ai ajouté en expliquant pourquoi c'est une très, très mauvaise idée.

Non, vraiment: Comment faire pour lever l'ambiguïté entre les deux constructeurs lorsque les deux paramètres de type sont les mêmes?

Comme ceci:

static Either<A, B> First<A, B>(A a) => new Either<A, B>(a);
static Either<A, B> Second<A, B>(B b) => new Either<A, B>(b);
...
var ess1 = First<string, string>("hello");
var ess2 = Second<string, string>("goodbye");

qui est la façon dont la classe doit avoir été conçu en premier lieu. L'auteur de l' Either classe devrait avoir écrit

class Either<A, B> 
{
  private Either(A a) { ... }
  private Either(B b) { ... }
  public static Either<A, B> First(A a) => new Either<A, B>(a);
  public static Either<A, B> Second(B b) => new Either<A, B>(b);
  ...
}
...
var ess = Either<string, string>.First("hello");

3voto

musefan Points 23208

La seule façon que je pouvais penser, serait d'utiliser la réflexion pour parcourir chaque constructeur et ensuite déterminer ce qui doit être utilisée en se basant sur le corps de la méthode.

Bien sûr, c'est beaucoup au-dessus et vous devriez vraiment juste de refactoriser votre classe, mais c'est une solution de travail.

Il vous faut donc identifier l' byte[] pour le corps de la méthode que vous souhaitez utiliser et 'code' que dans le programme (ou de lire à partir du fichier, etc.). Bien sûr, vous devez être très prudent que le corps de la méthode peut changer au fil du temps, par exemple si la classe est modifiée à tout moment.

// You need to set (or get from somewhere) this byte[] to match the constructor method body you want to use.
byte[] expectedMethodBody = new byte[] { 0 };

Either<string, string> result = null; // Will hold the result if we get a match, otherwise null.
Type t = typeof(Either<string, string>); // Get the type information.

// Loop each constructor and compare the method body.
// If we find a match, then we invoke the constructor and break the loop.
foreach (var c in t.GetConstructors())
{
    var body = c.GetMethodBody();
    if (body.GetILAsByteArray().SequenceEqual(expectedMethodBody))
    {
        result = (Either<string, string>)c.Invoke(new object[] { "123" });
        break;
    }
}

Avertissement: Bien que j'ai testé ce code brièvement et il semble fonctionner, je suis vraiment sceptique sur la façon fiable, il est. Je ne connais pas assez sur le compilateur pour être à l'aise en disant que le corps de la méthode ne changeront pas sur une re-compilation, même si le code n'est pas modifié. Il est peut-être qu'il serait plus fiable si cette classe a été définie dans un pré-DLL compilée, encore une fois je ne sais pas pour sûr.


Il y a peut être d'autres informations que vous pourriez obtenir via la réflexion qui pourrait rendre plus facile à identifier le bon constructeur. Cependant, ce fut la première qui vient à l'esprit et je n'ai pas vraiment regardé dans toutes les autres options possibles en ce moment.

Il serait beaucoup plus simple si nous pouvions compter sur l'ordre des constructeurs, mais comme il est cité à partir de MSDN, il n'est pas fiable:

Le GetConstructors méthode ne retourne pas de constructeurs dans un ordre particulier, tels que l'ordre de déclaration. Votre code ne doit pas dépendre de l'ordre dans lequel les constructeurs sont renvoyés, parce que cet ordre varie.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X