Все вроде бы и знают, но как будто никто не хочет говорить об этом. Во всяком случае мне не попадались статьи, где это разобрано. Ну давайте я расскажу.
Говорить будем, как вы поняли, о расширяющих методах, которые появились аж в далеком .NET 3.0.
Сперва заглянем в документацию:
Давайте рассмотрим пример:
Давайте еще раз заглянем в документацию, чтобы определить, какое место расширяющий метод имеет в порядке выбора метода для вызова:
В каких случаях можно ошибиться при вызове расширяющего метода? Когда использование расширяющего метода выглядит, как использования наследования. Например, когда вы пытаетесь вернуть из базы набор сущностей с отложенной фильрацией.
Говорить будем, как вы поняли, о расширяющих методах, которые появились аж в далеком .NET 3.0.
Сперва заглянем в документацию:
То есть сделано все, чтобы итоговый пользователь типа не смог отличить обыкновенный метод от расширяющего.Методы расширения позволяют "добавлять" методы в существующие типы без создания нового производного типа, перекомпиляции или иного изменения исходного типа. Методы расширения представляют собой особую разновидность статического метода, но вызываются так же, как методы экземпляра в расширенном типе. Для клиентского кода, написанного на языках C# и Visual Basic, нет видимого различия между вызовом метода расширения и вызовом методов, фактически определенных в типе.
Давайте рассмотрим пример:
Сколько раз будут выведены "Extension Where" и "Own Where"? Правильный ответ - нисколько. Ни родной метод PersonEnumerable, ни расширяющий метод IPersonEnumerable не будут вызваны, потому что у IEnumerable<T> есть свой вариант Where (стойте, стойте, у интерфейса есть своя реализация?).
- namespace ExtensionMethods
- {
- public class Person
- {
- public string Name { get; set; }
- public string LastName { get; set; }
- }
- public static class PersonEnumberableExtension
- {
- public static IEnumerable<Person> Where(this IPersonEnumerable pe, Func<Person, bool> func)
- {
- Console.WriteLine("Extension Where");
- foreach (var p in pe)
- if (func(p))
- yield return p;
- }
- }
- public interface IPersonEnumerable: IEnumerable<Person>, IEnumerable
- {
- IEnumerable<Person> Where(Func<Person, bool> func);
- }
- public class PersonIEnumerable : IPersonEnumerable
- {
- private IEnumerable<Person> _persons;
- public PersonIEnumerable(IEnumerable<Person> persons)
- {
- _persons = persons;
- }
- public IEnumerator<Person> GetEnumerator()
- {
- return _persons.GetEnumerator();
- }
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
- public IEnumerable<Person> Where(Func<Person, bool> func)
- {
- Console.WriteLine("Own Where");
- return PersonEnumberableExtension.Where(this, func);
- }
- }
- class Program
- {
- public static IEnumerable<Person> GetPersons()
- {
- var list = new List<Person>
- {
- new Person { Name = "Murad", LastName = "Muradov" },
- new Person { Name = "Artem", LastName = "Muradov" },
- new Person { Name = "Mikhail", LastName = "Ivanov" },
- new Person { Name = "Anton", LastName = "Ivanov" },
- new Person { Name = "Aleksandr", LastName = "Sergeev" },
- new Person { Name = "Anton", LastName = "Sergeev" }
- };
- return new PersonIEnumerable(list);
- }
- static void Main(string[] args)
- {
- var persons = GetPersons().Where(p => p.Name == "Anton" || p.Name == "Murad" || p.Name == "Mikhail");
- var one = persons.Where(x => x.LastName == "Ivanov");
- foreach (var a in one)
- Console.WriteLine(a.Name + " " + a.LastName);
- Console.ReadKey();
- }
- }
- }
Давайте еще раз заглянем в документацию, чтобы определить, какое место расширяющий метод имеет в порядке выбора метода для вызова:
Но тут имеют ввиду только конкретный тип, методы которого расширяют! То есть, если у IEnumerable<T> есть расширяющий метод, который подходит под сигнатуру, то именно он и будет вызван. Даже несмотря на то, что объект, который скрывается под IEnumerable<T>, имеет свою реализацию метода или свой расширяющий метод. Тут логика "виртуальной таблицы" не работает. Совершенно непохоже на механизмы наследования. Это центральное отличие расширяющих методов от методов "из типа".Методы расширения можно использовать для расширения класса или интерфейса, но не для их переопределения. Метод расширения, имеющий те же имя и сигнатуру, что и интерфейс или метод класса, никогда не вызывается. Во время компиляции методы расширения всегда имеют более низкий приоритет, чем методы экземпляра, определенные в самом типе.
В каких случаях можно ошибиться при вызове расширяющего метода? Когда использование расширяющего метода выглядит, как использования наследования. Например, когда вы пытаетесь вернуть из базы набор сущностей с отложенной фильрацией.
В примере используется EF, метод Set<T> возвращает реализацию IQueryable<T>. Программист подразумевал, что позже полученный IEnumerable<T> можно будет отложенно фильтровать при помощи Where. Надеялся, что будет вызвана реализация Where из IQueryable<T>, которая запомнит операцию для дальнейшего ее использования при генерации запроса в базу. Однако, все произойдет по другому пути. IEnumerable<T>.Where в итоге получит все записи из базы, а потом применит к полученному предикаты из Where.
- public IEnumerable<Person> GetAll()
- {
- return _context.Set<Person>();
- }
Такие дела. Сахар сахаром (Linq), но нужно всегда поглядывать, какой именно метод ты вызываешь.
- public IEnumerable<Person> GetIvanovs()
- {
- var personRepository = new PersonRepository();
- return personRepository.GetAll().Where(x => x.Name == "Ivanov").ToList();
- }
Хм, а какая тут, собственно, проблема? Всё четко, если переменная IEnumerable - то вызывать соответсвующий метод. Если нужны твои вызовы, пиши
ОтветитьУдалитьpublic static PersonIEnumerable GetPersons()
и будет счастье
Тут не проблема, тут особенность расширяющих методов. IEnumerable.GetEnumerator - вызывается метод типа, который реализовал интерфейс. IEnumerable.Where - всегда вызывается расширяющий метод IEnumerable. В этом плане расширяющие методы совсем непохожи на обыкновенные.
УдалитьКстати реализация расширяющих методов IQueryable опирается на реализацию IQueryProvider. С большой поправкой можно назвать наследованием.
Ну почему непохожи, мне эта ситуация напоминает сокрытие методов, например вот такое
Удалитьpublic class BaseClass
{
public void Foo ()
{
Console.WriteLine("base");
}
}
public class Inherit : BaseClass
{
public new void Foo()
{
Console.WriteLine("Inherit");
}
}
void Main()
{
BaseClass temp = new Inherit();
temp.Foo();
}
Когда скрывают методы, обычно дополняют функционал, а не заменяют. А так да, так тоже можно "сломать наследование".
Удалить