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

Динамический, linq и Select()

Учитывая следующее (бессмысленное, но это для иллюстративной цели) тестовый класс:

public class Test
{
    public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
    {
        return t.Select(x => ToStr(x));
    }

    public IEnumerable<string> ToEnumerableStrsWillCompile(IEnumerable<dynamic> t)
    {
        var res = new List<string>();

        foreach (var d in t)
        {
            res.Add(ToStr(d));
        }

        return res;
    }

    public string ToStr(dynamic d)
    {
        return new string(d.GetType());
    }
}

Почему он не компилируется со следующей ошибкой, на t.Select(x => ToStr(x))?

Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<dynamic>' 
to 'System.Collections.Generic.IEnumerable<string>'. An explicit conversion 
exists (are you missing a cast?)

Ошибка во втором методе.

4b9b3361

Ответ 1

Я считаю, что здесь происходит то, что, поскольку выражение ToStr(x) включает переменную dynamic, весь тип результата выражения также также dynamic; поэтому компилятор считает, что он имеет IEnumerable<dynamic>, где он ожидает IEnumerable<string>.

public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
    return t.Select(x => ToStr(x));
}

Есть два способа исправить это.

Использовать явное выражение:

public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
    return t.Select(x => (string)ToStr(x));
}

Это говорит компилятору, что результат выражения определенно будет строкой, поэтому мы получим IEnumerable<string>.

Заменить лямбда на группу методов:

public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
    return t.Select(ToStr);
}

Таким образом, компилятор неявно преобразует выражение группы методов в лямбда. Заметим, что, поскольку в выражении не упоминается переменная dynamic x, тип ее результата можно сразу определить как string, потому что есть только один метод, и его тип возврата string.

Ответ 2

Попробуйте вот так:

public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
    return t.Select(ToStr);
}

Другая возможность - явно указать общие аргументы:

public IEnumerable<string> ToEnumerableStrsWontCompile(IEnumerable<dynamic> t)
{
    return t.Select<dynamic, string>(x => ToStr(x));
}

Ответ 3

Попробуйте return t.Select(x => ToStr(x)) as IEnumerable<string>

Ответ 4

казалось бы, что компилятор С# определяет тип лямбда в первом методе x => ToStr(x) как Func<dynamic, dynamic> и поэтому объявляет тип IEnumerable, возвращаемый как IEnumerable<dynamic>. Небольшое изменение x => (string)ToStr(x) похоже на его исправление.

Это, скорее всего, из-за правил вывода типа - потому что если вы измените строку на это:

return t.Select<dynamic, string>(x => ToStr(x));

Он компилируется без ошибок.

Определенное правило вывода типа, о котором идет речь, однако, я не слишком уверен в этом - однако, если вы возьмете этот код:

public void foo(dynamic d)
{
  var f = this.ToStr(d);
  string s = f;
}

И затем наведите указатель мыши на "f" в редакторе, вы увидите, что intellisense сообщает тип выражения как "динамический f". Это будет связано с тем, что this.ToStr(d) является динамическим выражением, независимо от того, известен ли сам метод и его тип возврата во время компиляции.

Затем компилятор с удовольствием назначает string s = f;, потому что он способен статически анализировать тип, который может быть f, потому что в конечном итоге ToStr всегда возвращает строку.

Вот почему для первого метода требуются параметры с литым или явным типом - поскольку компилятор делает ToStr типом dynamic; потому что в нем есть хотя бы одно динамическое выражение.