Почему C # не связывается правильно с общими переопределенными методами? - PullRequest
8 голосов
/ 07 января 2011

Я определил следующие классы и методы:

 using System;
 using System.Linq.Expressions;
 using System.Windows.Forms;

 public class ReturnValue<T, S> {}

 public class Something<T>
 {
     // Sorry about the odd formatting. Trying to get it to fit nicely...
     public ReturnValue<T, C>
     Do<C, S>(C control, Expression<Func<C, S>> controlProperty)
     where C : Control
     {
         return new ReturnValue<T, C>();
     }

     public ReturnValue<T, ToolStripItem>
     Do<S>(ToolStripItem control, Expression<Func<ToolStripItem, S>> controlProperty)
     {
         return new ReturnValue<T, ToolStripItem>();
     }
 }

Это хорошо компилируется. Woo Hoo! Почти на месте. Затем я попытаюсь использовать его позже с кодом, подобным этому:

 var toolStripItem = new ToolStripStatusLabel();

 var something = new Something<string>();
 something.Do(toolStripItem, t => t.Text); // Does not compile

Это, однако, умирает со следующим сообщением об ошибке

Тип ToolStripStatusLabel нельзя использовать в качестве параметра типа C в универсальном типе или методе Something<T>.Do<C,S>(C, Expression<Func<C,S>>). Не существует неявного преобразования ссылок из ToolStripStatusLabel в Control.

Мне кажется, что в этом случае произошел сбой компилятора C #, хотя эти два метода не создают набор неоднозначных объявлений методов. Control и ToolStripStatusLabel существуют как братья и сестры в дереве наследования Component. Я думаю, что у компилятора будет достаточно информации, чтобы правильно связать вызов метода в клиентском коде.

Однако, если я делаю то же самое с моими собственными классами братьев и сестер, то все компилируется нормально.

 public class Parent {}
 public class Child1 : Parent {}
 public class Child2 : Parent {}

 public class Something2<T>
 {
     public ReturnValue<T, C>
     Do<C, S>(C control, Expression<Func<C, S>> controlProperty)
     where C : Child1
     {
         return new ReturnValue<T, C>();
     }

     public ReturnValue<T, Child2>
     Do<S>(Child2 control, Expression<Func<Child2, S>> controlProperty)
     {
         return new ReturnValue<T, Child2>();
     }
 }

 var child2 = new Child2();
 var something2 = new Something2<string>();
 something2.Do(child2, c => c.GetType()); // Compiles just fine

Может кто-нибудь пролить свет на то, что я сделал неправильно, если что-нибудь?

Ответы [ 2 ]

11 голосов
/ 07 января 2011

Проблема заключается в том, что первый метод имеет значение в наборе кандидатов для разрешения перегрузки, поскольку ограничение типа C : Control применяется только после выполнения разрешения перегрузки . Я полагаю, вы ожидаете, что это будет отсеяно рано - и это не так.

Теперь, если вы обрабатываете C = ToolStripItem, первая перегрузка более специфична, чем вторая, поэтому результатом разрешения перегрузки является выбор этой первой версии.

Проверка ограничения типа: , затем применен ... и завершается неудачей.

У меня есть сообщение в блоге по этому вопросу , которое может помочь вам понять процесс, а затем другое сообщение в блоге , где я довольно глупо применяю правила.

РЕДАКТИРОВАТЬ: во втором примере тип аргумента равен точно тип, указанный в первом параметре, поэтому первый метод не будет более конкретным. Второй метод выигрывает из-за меньшего количества параметров типа (я думаю, я не проверял его подробно), а затем проверяется и проходит.

Чтобы перевести его обратно в термины ToolStripItem, вы могли бы фактически сделать свой первый пример компиляции с одним простым изменением:

// Change this
var toolStripItem = new ToolStripStatusLabel();
// To this...
ToolStripItem toolStripItem = new ToolStripStatusLabel();

Изменение типа времени компиляции toolStripItem с ToolStripStatusLabel на ToolStripItem лишает «преимущества» первого метода, поэтому он компилируется.

0 голосов
/ 07 января 2011

Я думаю, что вам просто нужно быть более точным с вашим звонком:

var toolStripItem = new ToolStripStatusLabel();
var something = new Something<string>();
something.Do<string>(toolStripItem, t => t.Text); // might compile
...