Динамический, 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?)
Ошибка во втором методе.
Ответы
Ответ 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
; потому что в нем есть хотя бы одно динамическое выражение.