Disambigutate между двумя конструкторами, когда два параметра типа одинаковы - программирование
Подтвердить что ты не робот

Disambigutate между двумя конструкторами, когда два параметра типа одинаковы

Учитывая

class Either<A, B> {

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

Как устранить неоднозначность между двумя конструкторами, когда два параметра типа одинаковы?

Например, эта строка:

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

Сбой:

Вызов неоднозначен между следующими методами или свойствами: "Программа. Или. (A)" и "Program.Either.Either(B)"

Я знаю, что если бы я дал параметрам разные имена (например, A a и B b вместо просто x), я мог бы использовать именованные параметры для устранения неоднозначности (например, new Either<string, string>(a: "")). Но мне интересно узнать, как решить эту проблему, не изменяя определения Either.

Edit:

Вы можете написать пару интеллектуальных конструкторов, но мне интересно узнать, можно ли конструкторы Either вызывать напрямую без двусмысленности. (Или если есть другие "трюки" помимо этого).

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>("");
4b9b3361

Ответ 1

Как устранить двузначность между двумя конструкторами, когда два параметра типа одинаковы?

Начну с того, что не отвечу на ваш вопрос, а затем закончу его фактическим ответом, который позволит вам обойти эту проблему.

Вам не нужно, потому что вы никогда не должны попасть в эту позицию в первую очередь. Ошибка проектирования для создания типичного типа, который может привести к тому, что подписи подписей будут унифицированы таким образом. Никогда не пишите такой класс.

Если вы вернетесь и прочитаете исходную спецификацию С# 2.0, вы увидите, что исходный дизайн должен был, чтобы компилятор обнаружил общие типы, в которых он был каким-либо образом возможен для возникновения такой проблемы, и чтобы сделать объявление класса запрещено. Это внесло это в опубликованную спецификацию, хотя это была ошибка; команда разработчиков поняла, что это правило слишком строгое из-за сценариев, таких как:

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

Было бы странно говорить, что этот класс является незаконным, потому что вы можете сказать C<Stream>, а затем не сможете однозначно устранить конструкторы. Вместо этого было добавлено правило для разрешения перегрузки, в котором говорится, что если есть выбор между (Stream) и (T where Stream is substituted for T), тогда выигрывает первая.

Таким образом, правило, что этот вид объединения является незаконным, было отменено, и теперь это разрешено. Однако очень, очень плохая идея - создавать типы, которые объединяются таким образом. В некоторых случаях CLR плохо справляется с этим, и это смущает компилятор и разработчиков. Например, вы хотели бы угадать вывод этой программы?

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

Если вы скомпилируете это с включенными предупреждениями, вы увидите предупреждение, которое я добавил, объясняя, почему это действительно и очень плохая идея.

Нет, действительно: как устранить двузначность между двумя конструкторами, когда два параметра типа одинаковы?

Вот так:

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

, каким образом должен был быть разработан класс в первую очередь. Автор класса Either должен был написать

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

Ответ 2

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

Конечно, это путь сверху, и вы действительно должны просто реорганизовать свой класс, но это рабочее решение.

Для этого требуется идентифицировать byte[] для тела метода, который вы хотите использовать, и "жесткий код", который содержится в программе (или читать из файла и т.д.). Конечно, вам нужно быть очень осторожным, чтобы тело метода могло меняться со временем, например, если класс изменен в любой точке.

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

Отказ от ответственности. Хотя я кратко протестировал этот код и, похоже, работает, я действительно скептически отношусь к тому, насколько он надежный. Я не знаю достаточно о компиляторе, чтобы было удобно говорить, что тело метода не изменится при повторной компиляции, даже если код не изменился. Возможно, станет более надежным, если бы этот класс был определен в предварительно скомпилированной DLL, но я не знаю наверняка.


Может быть другая информация, которую вы могли бы получить через отражение, которое могло бы облегчить идентификацию правильного конструктора. Тем не менее, это было первым, что пришло мне в голову, и я на самом деле не смотрел на другие возможные варианты.

Было бы намного проще, если бы мы могли полагаться на порядок конструкторов, но как указано в MSDN, это ненадежно:

Метод GetConstructors не возвращает конструкторы в определенном порядке, такие как порядок объявления. Ваш код не должен зависеть от порядка, в котором возвращаются конструкторы, потому что этот порядок меняется.