Вывод типа через IEnumerable<T>
-
11-09-2019 - |
Вопрос
В стеке уже есть несколько сообщений о подобных вещах, но они не совсем одинаковы, поэтому заранее извиняюсь, если на этот вопрос уже был дан ответ.
Почему это не работает:
public class MyBase { }
public class MyUtils
{
public bool Foo<T> (T myObject) { return true; }
public bool Foo (MyBase myBaseObject) { return false; }
public void Go<T> (IEnumerable<T> items)
{
foreach (var item in items)
{
// this test fails
Assert.IsFalse (Foo (item));
}
}
}
Если я вызову Go() выше и передам загрузку объектов MyBase, каждый вызов Foo будет вызывать общий Foo(), который возвращает true.
new MyUtils ().Go (new MyBase[] { new MyBase (), new MyBase () });
Почему вместо этого он не вызывает специализированную версию MyBase?Если я вызываю Foo (new MyBase()) напрямую, он правильно определяет, какой вызов следует сделать.Это из-за отсутствия ковариации коллекций в C#3, или я просто глуп и делаю это неправильно?
Спасибо!
Исаак
Решение
Он не вызывает «специализированный», поскольку компилятор выбирает, какой метод вызывать, когда программа (которая в данном случае является Go
функция) скомпилировано, нет когда он запускается.
Когда компилятор компилирует Go
функция, единственная информация, которую она имеет, это то, что существует некоторый объект типа T
.Он понятия не имеет, что в какой-то более поздний момент вы можете предоставить ему объект типа MyBase
.Единственный вариант, который у него есть, это выбрать Foo<T>
перегрузки, и поэтому она включает это в скомпилированную программу.
Если вы хотите, чтобы приложение выбирало перегрузки во время выполнения и выбирало лучшую перегрузку, просматривая объект во время работы приложения, это называется «динамической диспетчеризацией» и используется только динамическими языками, такими как Ruby, Python, PHP. , и т. д.
C#3 полностью статичен и не поддерживает это.Если вы хотите, чтобы он работал таким образом, вам придется написать в своем коде оператор if, чтобы проверить тип.C#4, с другой стороны, имеет некоторую динамическую поддержку.Если бы вы писали этот код на C# 4, вы могли бы объявить функцию Go следующим образом:
public void Go<T> (IEnumerable<dynamic> items)
Затем он будет использовать динамическую диспетчеризацию во время выполнения, чтобы выбрать, какая перегрузка вызывается, и будет вызывать перегрузку, специализированную для выполнения MyBase
Другие советы
Согласно спецификациям C# (7.4.3.2), неуниверсальный метод лучше универсального, поэтому его следует выбрать.
Но я думаю, что в вашем случае выбран общий вариант, потому что он вызывается в общем контексте вызова (цикл foreach).