Если я передам IQueryable как IEnumerable, тогда вызывается метод расширения Linq, реализация которого вызывается?

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

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 и "Go to Definition", я попал в версию IEnumerable, что имеет смысл с визуальной точки зрения.

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


Edit: Оказывается, что, как указал Иридиум, это версия 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".

Ответы

Ответ 1

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

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

Ответ 2

Когда вы запускаете эту простую программу, она объясняет, как работает перегрузка:). Ответ заключается в том, что будет вызван метод базового типа. И он должен работать одинаково для 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";
    }
}