Подтвердить что ты не робот

Почему С# не выводит мои общие типы?

У меня много веселья Funcy (забавное предназначение) с помощью общих методов. В большинстве случаев вывод типа С# достаточно умен, чтобы выяснить, какие общие аргументы он должен использовать для моих общих методов, но теперь у меня есть проект, где компилятор С# не преуспевает, хотя я считаю, что ему удастся найти правильные типы.

Может ли кто-нибудь сказать мне, является ли компилятор немного глупым в этом случае, или есть очень ясная причина, почему он не может вывести мои общие аргументы?

Здесь код:

Определения классов и интерфейсов:

interface IQuery<TResult> { }

interface IQueryProcessor
{
    TResult Process<TQuery, TResult>(TQuery query)
        where TQuery : IQuery<TResult>;
}

class SomeQuery : IQuery<string>
{
}

Некорректный код, который не компилируется:

class Test
{
    void Test(IQueryProcessor p)
    {
        var query = new SomeQuery();

        // Does not compile :-(
        p.Process(query);

        // Must explicitly write all arguments
        p.Process<SomeQuery, string>(query);
    }
}

Почему это? Что мне здесь не хватает?

Здесь сообщение об ошибке компилятора (это не оставляет нам ничего хорошего):

Аргументы типа для метода IQueryProcessor.Process(TQuery) не могут быть выведены из использования. Попробуйте указать введите аргументы явно.

Я полагаю, что С# должен иметь возможность сделать вывод, что это связано с тем, что:

  • Я предоставляю объект, который реализует IQuery<TResult>.
  • Только версия IQuery<TResult>, которая реализует тип, имеет значение IQuery<string> и, следовательно, TResult должен быть string.
  • С этой информацией у компилятора есть TResult и TQuery.

Решение

Для меня лучшим решением было изменить интерфейс IQueryProcessor и использовать динамическую типизацию в реализации:

public interface IQueryProcessor
{
    TResult Process<TResult>(IQuery<TResult> query);
}

// Implementation
sealed class QueryProcessor : IQueryProcessor {
    private readonly Container container;

    public QueryProcessor(Container container) {
        this.container = container;
    }

    public TResult Process<TResult>(IQuery<TResult> query) {
        var handlerType =
            typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
        dynamic handler = container.GetInstance(handlerType);
        return handler.Handle((dynamic)query);
    }
}

Интерфейс IQueryProcessor теперь принимает параметр IQuery<TResult>. Таким образом, он может вернуть TResult, и это решит проблемы с точки зрения потребителя. Нам нужно использовать отражение в реализации, чтобы получить фактическую реализацию, так как нужны конкретные типы запросов (в моем случае). Но здесь идет динамичная печать на помощь, которая будет отражать нас. Подробнее об этом можно прочитать в статье .

4b9b3361

Ответ 1

Куча людей указала, что С# не делает выводы на основе ограничений. Это правильно и актуально для вопроса. Выводы сделаны путем изучения аргументов и их соответствующих формальных типов параметров, и это единственный источник информации о выводах.

После этого связка связана с этой статьей:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

Эта статья является устаревшей и не имеет отношения к вопросу. Он устарел, потому что он описывает решение по дизайну, которое мы сделали в С# 3.0, которое затем было отменено на С# 4.0, в основном на основе ответа на эту статью. Я только что добавил обновление к этой статье.

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

Соответствующая статья, которую я читаю, скорее всего такова:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

Ответ 2

С# не будет вызывать общие типы на основе возвращаемого типа общего метода, только аргументы метода.

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

Подробнее см. сообщение Эрика Липперта по теме.

Ответ 3

Он не использует ограничения для вывода типов. Скорее, он вводит типы (когда это возможно), а затем проверяет ограничения.

Поэтому, хотя единственный возможный TResult, который можно использовать с параметром SomeQuery, он не увидит этого.

Заметим также, что было бы возможно, что SomeQuery также реализовать IQuery<int>, что является одной из причин, по которой это ограничение для компилятора может быть плохой идеей.

Ответ 4

Спецификация достаточно четко описывает это:

Раздел 7.4.2. Вывод типа

Если предоставленное количество аргументов отличается от количества параметров в методе, то вывод немедленно не выполняется. В противном случае предположим, что общий метод имеет следующую подпись:

Tr M (T1 x1... Tm xm)

При вызове метода формы M (E1... Em) задача вывода типа находит уникальные аргументы типа S1... Sn для каждого из параметров типа X1... Xn, так что вызов M (E1... Em) становится действительным.

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

Компилятор не просто предполагает, что вы хотели string как аргумент TResult, и не могли. Представьте себе TResult, полученный из строки. Оба будут действительны, так что выбрать? Лучше быть явным.

Ответ 5

Почему ответили хорошо, но есть альтернативное решение. Я сталкиваюсь с одинаковыми проблемами регулярно, однако dynamic или любое решение, использующее отражение или распределение данных, в моем случае не имеет значения (радость видеоигр...)

Поэтому вместо этого я передаю результат как параметры out который затем правильно выведен.

interface IQueryProcessor
{
     void Process<TQuery, TResult>(TQuery query, out TResult result)
         where TQuery : IQuery<TResult>;
}

class Test
{
    void Test(IQueryProcessor p)
    {
        var query = new SomeQuery();

        // Instead of
        // string result = p.Process<SomeQuery, string>(query);

        // You write
        string result;
        p.Process(query, out result);
    }
}

Единственным недостатком, о котором я могу думать, является то, что он запрещает использование "var".