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>("");
Ответы
Ответ 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 не возвращает конструкторы в определенном порядке, такие как порядок объявления. Ваш код не должен зависеть от порядка, в котором возвращаются конструкторы, потому что этот порядок меняется.