Универсальные методы (руководство по программированию на C#)

Универсальным называется метод, объявленный с использованием параметров типа, как показано ниже:

static void Swap<T>(ref T lhs, ref T rhs)
{
    T temp;
    temp = lhs;
    lhs = rhs;
    rhs = temp;
}

В следующем примере кода показан один из способов вызова метода с использованием int в качестве аргумента типа:

public static void TestSwap()
{
    int a = 1;
    int b = 2;

    Swap<int>(ref a, ref b);
    System.Console.WriteLine(a + " " + b);
}

Если опустить аргумент типа, компилятор определит его. Следующий вызов Swap эквивалентен предыдущему:

Swap(ref a, ref b);

Для статических методов и методов экземпляра применяются одинаковые правила определения типа. Компилятор может определить параметры типа на основе переданных ему аргументов метода. Определение параметров типа невозможно только для ограничения или возвращаемого значения. Соответственно, механизм определения типа не работает с методами, не имеющими параметров. Определение типа происходит во время компиляции до того, как компилятор попытается разрешить сигнатуры перегруженных методов. Компилятор применяет логику определения типа ко всем универсальным методам с одинаковым именем. На этапе разрешения перегрузки компилятор включает только те универсальные методы, для которых был успешно определен тип.

В универсальном классе методы, не являющиеся универсальными, могут получать доступ к параметрам типа на уровне класса следующим образом:

class SampleClass<T>
{
    void Swap(ref T lhs, ref T rhs) { }
}

Если определить универсальный метод, принимающий те же параметры типа, что и содержащий его класс, возникнет предупреждение компилятора CS0693. Это связано с тем, что в области действия метода предоставленный для внутреннего типа T аргумент скрывает аргумент, предоставленный для внешнего типа T. Для большей гибкости можно вызывать метод универсального класса с использованием аргументов типа, отличающихся от предоставленных при создании экземпляра класса. В этом случае следует предоставить другой идентификатор для параметра типа метода, как показано в GenericList2<T> в следующем примере.

class GenericList<T>
{
    // CS0693.
    void SampleMethod<T>() { }
}

class GenericList2<T>
{
    // No warning.
    void SampleMethod<U>() { }
}

С помощью ограничений можно реализовать специализированные операции в отношении параметров типа в методах. Эта версия Swap<T> теперь называется SwapIfGreater<T> и может использоваться только с типами, реализующими IComparable<T>.

void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
{
    T temp;
    if (lhs.CompareTo(rhs) > 0)
    {
        temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}

Универсальные методы могут быть перегружены в нескольких параметрах типа. Например, все представленные ниже методы могут располагаться в одном классе:

void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }

Вы также можете использовать параметр типа в качестве возвращаемого типа метода. В следующем примере кода показан метод, возвращающий массив типа T:

T[] Swap<T>(T a, T b)
{
    return [b, a];
}

Спецификация языка C#

Дополнительные сведения см. в спецификации языка C#.

См. также