0Pricing
C# Academy · Урок

Выбор метода расширения

Разберитесь, как компилятор находит методы расширения.

«Выбор метода расширения» — бесплатный урок C# Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Как компилятор находит методы расширения

Когда вы пишете value.Method(), а подходящего метода экземпляра нет, компилятор ищет метод расширения. Однако он рассматривает только методы расширения, пространство имён которых находится в области видимости благодаря директиве using.

Директивы импорта делают методы расширения доступными

Метод расширения виден только после импорта его пространства имён. Без подходящей директивы using кажется, что метода не существует, хотя он скомпилирован в сборку.

using System;
using MyHelpers; // brings StringExtensions into scope

namespace MyHelpers
{
    public static class StringExtensions
    {
        public static string Reverse(this string s)
        {
            var arr = s.ToCharArray();
            Array.Reverse(arr);
            return new string(arr);
        }
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("hello".Reverse());
    }
}

Сначала проверяются методы экземпляра

Компилятор разрешает вызов поэтапно: сначала он ищет подходящий метод экземпляра, включая унаследованные методы. Только если подходящего метода нет, он ищет методы расширения.

using System;
using System.Collections.Generic;

public static class ListExt
{
    // Ignored: List<int> already has Add(int)
    public static void Add(this List<int> list, int a, int b)
        => list.Add(a + b);
}

public class Program
{
    public static void Main()
    {
        var list = new List<int>();
        list.Add(5); // the built-in instance Add wins for this signature
        Console.WriteLine(list[0]);
    }
}

Предпочтение ближайшим пространствам имён

Если подходят два метода расширения, выигрывает тот, чьё пространство имён находится ближе в иерархии вложенности. Если компилятор не может устранить неоднозначность, возникает ошибка компиляции, и вам нужно явно указать нужный вариант.

using System;

namespace Outer
{
    public static class A { public static string Tag(this int n) => "A" + n; }

    namespace Inner
    {
        public static class B { public static string Tag(this int n) => "B" + n; }

        public class Program
        {
            public static void Main()
            {
                // B is in the nearer (Inner) namespace, so it is preferred
                Console.WriteLine(7.Tag());
            }
        }
    }
}

Устранение неоднозначности статическим вызовом

Если методы расширения конфликтуют или вы хотите сделать вызов явным, вызовите статический метод напрямую, указав полное имя его класса. Это полностью обходит разрешение методов расширения.

using System;

public static class FormatExt
{
    public static string Bracket(this string s) => "[" + s + "]";
}

public class Program
{
    public static void Main()
    {
        // Explicit static call, no ambiguity possible
        Console.WriteLine(FormatExt.Bracket("x"));
    }
}

Разрешение перегрузок среди методов расширения

Если несколько методов расширения имеют одно имя, обычное разрешение перегрузок выбирает наиболее подходящий вариант по типам параметров, как и при обычной перегрузке методов.

using System;

public static class PrintExt
{
    public static string Describe(this int n) => "int:" + n;
    public static string Describe(this string s) => "str:" + s;
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine(42.Describe());
        Console.WriteLine("hi".Describe());
    }
}

Вывод обобщённых параметров при разрешении

Для обобщённых методов расширения компилятор выводит аргументы типов из получателя и аргументов. Если вывести их не удаётся, метод расширения не рассматривается.

using System;
using System.Collections.Generic;

public static class SeqExt
{
    public static T FirstItem<T>(this IEnumerable<T> source)
    {
        foreach (var item in source) return item;
        throw new InvalidOperationException("empty");
    }
}

public class Program
{
    public static void Main()
    {
        var nums = new List<int> { 5, 6, 7 };
        // T inferred as int from the List<int> receiver
        Console.WriteLine(nums.FirstItem());
    }
}

Глобальные импорты и неявные импорты

Современный C# позволяет объявлять директивы global using, благодаря чему пространство имён находится в области видимости всего проекта. Это делает общие библиотеки методов расширения доступными повсюду без повторения директив импорта.

using System;

// In a real project a separate file might say: global using MyHelpers;
namespace MyHelpers
{
    public static class IntExt { public static int Twice(this int n) => n * 2; }
}

public class Program
{
    public static void Main()
    {
        // Works here because MyHelpers is referenced explicitly
        Console.WriteLine(MyHelpers.IntExt.Twice(8));
    }
}

Когда важен тип получателя

Разрешение зависит от статического типа получателя, а не от его типа во время выполнения. Метод расширения, объявленный для базового типа, применяется даже тогда, когда переменная содержит экземпляр производного типа, если только в области видимости нет более специализированного метода расширения.

using System;

public class Animal { }
public class Cat : Animal { }

public static class AnimalExt
{
    public static string Kind(this Animal a) => "animal";
}

public class Program
{
    public static void Main()
    {
        Animal a = new Cat();
        // Chosen by static type Animal
        Console.WriteLine(a.Kind());
    }
}

Модель разрешения в мыслях

Представляйте порядок так: (1) методы экземпляра, затем (2) методы расширения из импортированных пространств имён — начиная с ближайших, затем (3) разрешение перегрузок среди кандидатов. Знание этого порядка объясняет почти любой неожиданный результат.

using System;

public static class Ext
{
    public static string Wrap(this string s) => "<" + s + ">";
}

public class Program
{
    public static void Main()
    {
        // No instance Wrap exists, so the extension is used
        Console.WriteLine("node".Wrap());
    }
}

Попробуйте сами

Определите метод расширения в пространстве имён, импортируйте его и вызовите. Затем вызовите тот же метод явно как статический, чтобы увидеть, как работают оба способа.

using System;
using Text.Helpers;

namespace Text.Helpers
{
    public static class StringExtensions
    {
        public static int Vowels(this string s)
        {
            int count = 0;
            foreach (var c in s.ToLower())
                if ("aeiou".IndexOf(c) >= 0) count++;
            return count;
        }
    }
}

public class Program
{
    public static void Main()
    {
        Console.WriteLine("education".Vowels());
        Console.WriteLine(Text.Helpers.StringExtensions.Vowels("education"));
    }
}

Быстрая проверка

Подумайте, что делает метод расширения видимым.

Итоги

Разрешение методов расширения выполняется в чётком порядке.

  • Сначала проверяются методы экземпляра; методы расширения лишь дополняют их.
  • Метод расширения виден только тогда, когда его пространство имён находится в области видимости через using или global using.
  • Побеждают ближайшие пространства имён; неразрешённые совпадения приводят к ошибкам компиляции.
  • Для устранения неоднозначности всегда можно явно вызвать статический метод.
  • При разрешении используется статический тип получателя.

Часто задаваемые вопросы

Урок «Выбор метода расширения» бесплатный?

Да — полный текст урока «Выбор метода расширения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Выбор метода расширения»?

Разберитесь, как компилятор находит методы расширения. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Выбор метода расширения»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Определение методов расширения
  2. Расширение интерфейсов и обобщённых типов
  3. Выбор метода расширения
  4. Проектирование хороших расширений
← Назад к C# Academy