Если я приведу IQueryable как IEnumerable, то вызову метод расширения Linq, какая реализация будет вызвана? - PullRequest
15 голосов
/ 07 января 2011

Учитывая следующий код:

IQueryable<T> queryable;

// something to instantiate queryable

var enumerable = (IEnumerable<T>) queryable;

var filtered = enumerable.Where(i => i > 3);

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

Это IEnumerable<T>.Where(...)? Или будет вызван IQueryable<T>.Where(...), потому что фактическая реализация по-прежнему явно запрашиваемая?

Предположительно, идеальным вариантом будет вызов версии IQueryable, точно так же, как нормальный полиморфизм всегда будет использовать более конкретное переопределение.

В Visual Studio, однако, когда я щелкаю правой кнопкой мыши по методу Where и «Перейти к определению», меня переводят в версию IEnumerable, что имеет смысл с визуальной точки зрения.

Моя главная проблема в том, что если где-нибудь в моем приложении я использую Linq для NHibernate, чтобы получить Queryable, но я передаю его с помощью интерфейса, использующего более общую сигнатуру IEnumerable, я потеряю чудеса отложенного выполнения базы данных!


Редактировать: Оказывается, что, как указал Иридиум, это Enumerable версия называется

.
public class Program
    {
        static void Main(string[] args)
        {
            var myString2 = new MyString2();
            var myString = (MyString)myString2;
            Console.WriteLine(myString.Method());
            Console.ReadLine();
        }
    }

    public class MyString {}

    public class MyString2 : MyString {}

    public static class ExtensionMethods
    {
        public static string Method(this MyString instance)
        {
            return "MyString method";
        }

        public static string Method(this MyString2 instance)
        {
            return "MyString2 method";
        }
    }

Вывод «Метод MyString».

Ответы [ 2 ]

9 голосов
/ 07 января 2011

В настоящее время принятый ответ касается виртуальных методов, а не методов расширения.

Если вы приведете IQueryable к IEnumerable, а затем вызовете один из методов расширения (например, где (...) в вашем вопросе)тогда будет вызвана версия Enumerable, а не Queryable.

3 голосов
/ 07 января 2011

Когда вы запускаете эту простую программу, она объясняет, как работает перегрузка :). Ответ в том, что метод базового типа будет вызван. И это должно работать одинаково для IQueryable и IEnumerable; то есть метод исходного объекта будет вызван, так как даже если мы приведем его к чему-то другому, фактическая реализация имеет исходный тип. Но когда есть метод расширения, используется метод расширения для объекта, на который ссылаются, поэтому в этом случае он зависит от логики в методе расширения. Метод расширения мог бы прийти к умному логиту, чтобы увидеть, имеет ли объект фактический тип, и затем привести его обратно, прежде чем он выполнит метод.

class Program
{
    static void Main(string[] args)
    {
        MyString2 myString2 = new MyString2();
        var myString = (MyString)myString2;
        Console.WriteLine(myString); // prints "ToString of MyString2"
        Console.WriteLine(myString.GiveMeTheString()); // prints "GiveMeTheString of MyString2"
        Console.ReadLine();
    }
}

public class MyString
{
    public string MyProperty { get; set; }
    public override string ToString()
    {
        return "ToString of MyString";
    }
}
public class MyString2 : MyString
{
    public string MyProperty { get; set; }
    public override string ToString()
    {
        return "ToString of MyString2";
    }
}

public static class Extensions
{
    public static string GiveMeTheString(this MyString myString)
    {
        return "GiveMeTheString of MyString";
    }
    public static string GiveMeTheString(this MyString2 myString)
    {
        return "GiveMeTheString of MyString2";
    }
}
...