Странность логического вывода типа интерфейса в c # - PullRequest
0 голосов
/ 17 мая 2018

C # не может вывести аргумент типа в этом довольно очевидном случае:

public void Test<T>(IEnumerable<KeyValuePair<string, T>> kvp)
{
    Console.WriteLine(kvp.GetType().Name + ": KeyValues");
}

Test(new Newtonsoft.Json.Linq.JObject());

Тип JObject явно реализует IEnumerable<KeyValuePair<string, JToken>>, но я получаю следующую ошибку:

CS0411: The type arguments for method cannot be inferred from the usage.

Почему это происходит?

UPD : редактору, отметившему этот вопрос как дубликат: обратите внимание, что подпись моего метода принимает не IEnumerable<T>, а IEnumerable<KeyValuePair<string, T>>. Тип JObject реализует IEnumerable дважды, но только одна из реализаций соответствует этому ограничению - поэтому не должно быть никакой двусмысленности.

UPD : Вот полное автономное воспроизведение без JObject: https://gist.github.com/impworks/2eee2cd0364815ab8245b81963934642

Ответы [ 3 ]

0 голосов
/ 17 мая 2018

Я подозреваю, что когда люди, которые разрабатывали C #, думали о том, как компилятор выведет тип для параметра универсального типа, они думали о возможных проблемах с этим.

Рассмотрим следующий сценарий:

class MyClass : JObject, IEnumerable<KeyValuePair<string, int>>
{
    public IEnumerator<KeyValuePair<string, int>> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

class Foo : MyClass { }

class FooBar : Foo { }

public void Test<T>(IEnumerable<KeyValuePair<string, T>> kvp)
{
   Console.WriteLine(kvp.GetType().Name + ": KeyValues");
}

var fooBar = new FooBar();
Test(fooBar);

К какому типу следует относить T?int или JToken?

Кроме того, насколько сложным должен быть алгоритм?

Вероятно, поэтому компилятор выводит тип только тогда, когда нет шансов на неоднозначность.Это было разработано таким образом и не без причины.

0 голосов
/ 17 мая 2018

Вот более простое воспроизведение:

interface I<T> {}
class X<T> {}
class Y {}
class Z {}  
class C : I<X<Y>>, I<Z> {}
public class P
{   
    static void M<T>(I<X<T>> i) { }
    public static void Main()
    {
        M(new C());
    }
}

Вывод типа не выполняется. Вы спрашиваете, почему и почему всегда трудно ответить на вопросы, поэтому позвольте мне перефразировать вопрос:

Какая строка спецификации запрещает этот вывод?

У меня есть моя копия спецификации C # 3; строка там выглядит следующим образом

  • V - это тип, который мы выводим от до , поэтому I<X<T>> в данном случае
  • U - это тип, который мы выводим из , поэтому C в данном случае

Это будет немного отличаться в C # 4, потому что я добавил ковариацию, но мы можем игнорировать это для целей этого обсуждения.

... если V является составным типом C<V1, … Vk> и существует уникальный набор типов U1, … Uk, такой, что существует неявное преобразование из U в C<U1, … Uk>, то точный вывод делается из каждого Ui к соответствующему Vi. В противном случае не делается никаких выводов.

Обратите внимание на слово уникальное там. НЕ существует уникального набора типов, так что C можно преобразовать в I<Something>, потому что оба X<Y> и Z действительны.

Вот еще один не-почему вопрос:

Какие факторы были учтены, когда команда дизайнеров приняла это решение?

Вы правы, что теоретически мы могли обнаружить в вашем случае, что X<Y> предназначен, а Z - нет. Если вы хотите предложить алгоритм вывода типа, который может обрабатывать неуникальные ситуации, подобные этой, которая никогда не допускает ошибки, - помните, Z может быть подтипом или супертипом X<Y> или X<Something Else> и I может быть ковариантным - тогда я уверен, что команда C # будет рада рассмотреть ваше предложение.

У нас был этот аргумент в 2005 году при разработке алгоритма логического вывода типа C # 3, и мы решили, что сценарии, в которых один класс реализует два одинаковых интерфейса, были редкими, а работа с этими редкими ситуациями создавала значительные сложности в языке. Эти сложности были бы дорогостоящими для разработки, определения, реализации и тестирования, и у нас были другие вещи, на которые можно было бы потратить деньги и усилия, что оказало бы большее влияние.

Кроме того, мы не знали, когда создавали C # 3, будем ли мы добавлять ковариацию в C # 4. Мы никогда не хотим вводить новую языковую функцию, которая делает возможную будущую языковую функцию невозможной или трудной . Лучше установить ограничения в языке сейчас и рассмотреть их снятие позже, чем выполнять большую работу для редкого сценария, который усложняет общий сценарий в следующей версии.

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

Вопрос, который вы не задавали, но вспоминаете:

Может ли сообщение об ошибке быть лучше?

Да. Я прошу прощения за то. Я проделал большую работу, сделав сообщения об ошибках разрешения перегрузки более описательными для распространенных сценариев LINQ, и я всегда хотел вернуться назад и сделать сообщения об ошибках логического вывода другого типа более понятными. Я написал вывод типа и код разрешения перегрузки, чтобы сохранить внутреннюю информацию, объясняющую, почему тип был выведен или перегрузка была выбрана или отклонена, как для моих собственных целей отладки, так и для создания лучших сообщений об ошибках, но я так и не удосужился разоблачить это информация для пользователя. Всегда было что-то более приоритетное.

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

0 голосов
/ 17 мая 2018

Проблема в следующем.

Если вы не укажете тип, тогда он попытается сделать вывод автоматически, но если он запутается, он выдаст указанное исключение.

  1. В вашем случае JObject реализовал интерфейс IEnumerable<KeyValuePair<string, JToken>>.

  2. Также JObject реализовал JContainer, и он также реализовал IEnumerable< JToken>.

Таким образом, когда он указан для T, он путается между IEnumerable<JToken> и IEnumrable<KeyValuePair<string, JToken>>.

...