Поделиться через


Ограничения параметров типа (Руководство по программированию на C#)

При определении универсального типа можно ограничить виды типов, которые могут использоваться клиентским кодом в качестве аргументов типа при инициализации соответствующего класса. При попытке клиентского кода создать экземпляр класса с помощью типа, который не допускается ограничением, в результате возникает ошибка компиляции. Это называется ограничениями. Ограничения определяются с помощью контекстно-зависимого ключевого слова where. В следующей таблице приведены шесть типов ограничений.

Ограничение

Описание

where T: struct

Аргумент типа должен иметь тип значения. Допускается указание любого типа значения, кроме Nullable. Дополнительные сведения см. в разделе Использование допускающих значение NULL типов (Руководство по программированию на C#).

where T : class

Аргумент типа должен иметь ссылочный тип; это также распространяется на тип любого класса, интерфейса, делегата или массива.

where T : new()

Аргумент типа должен иметь открытый конструктор без параметров. При использовании с другими ограничениями ограничение new() должно устанавливаться последним.

where T : <base class name>

Аргумент типа должен являться или быть производным от указанного базового класса.

where T : <interface name>

Аргумент типа должен являться или реализовывать указанный интерфейс. Можно установить несколько ограничений интерфейса. Ограничивающий интерфейс также может быть универсальным.

where T : U

Аргумент типа, предоставляемый в качестве T, должен совпадать с аргументом, предоставляемым в качестве U, или быть производным от него.

Применение ограничений

При необходимости проверить какой-либо элемент универсального списка, чтобы определить, является ли он допустимым, или сравнить его с другим элементом, компилятор должен иметь гарантию, что оператор или вызываемый им метод будет поддерживаться любым аргументом типа, который может определяться клиентским кодом. Такая гарантия обеспечивается применением одного или более ограничений к определению универсального класса. Например, ограничение базового класса сообщает компилятору, что в качестве аргументов типа будут использоваться только объекты определенного типа или являющиеся производными от этого типа. Получив такую гарантию, компилятор может разрешить вызов методов этого типа в универсальном классе. Ограничения накладываются с помощью контекстно-зависимого ключевого слова where. В следующем примере кода показаны функции, которые можно добавить к классу GenericList<T> (в разделе Введение в универсальные шаблоны. (Руководство по программированию на C#)) путем применения ограничения базового класса.

public class Employee
{
    private string name;
    private int id;

    public Employee(string s, int i)
    {
        name = s;
        id = i;
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int ID
    {
        get { return id; }
        set { id = value; }
    }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        private Node next;
        private T data;

        public Node(T t)
        {
            next = null;
            data = t;
        }

        public Node Next
        {
            get { return next; }
            set { next = value; }
        }

        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }

    private Node head;

    public GenericList() //constructor
    {
        head = null;
    }

    public void AddHead(T t)
    {
        Node n = new Node(t);
        n.Next = head;
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T FindFirstOccurrence(string s)
    {
        Node current = head;
        T t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property. 
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}

Это ограничение позволяет универсальному классу использовать свойство Employee.Name, потому что все элементы типа T гарантированно являются объектом Employee или объектом, наследующим от Employee.

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

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}

При наложении ограничений на параметр типа увеличивается число допустимых операций и вызовов методов, поддерживаемых ограничивающим типом и всеми типами в его иерархии наследования. Поэтому при разработке универсальных классов или методов, в случае выполнения какой-либо операции с универсальными членами помимо простого назначения или в случае вызова каких-либо методов, не поддерживаемых объектом System.Object, необходимо будет применить ограничения к параметру типа.

В случае применения ограничения where T : class следует избегать использования операторов == и != с параметром типа, потому что эти операторы выполняют только проверку удостоверения ссылки, но не проверяют равенство величины. Это верно даже в том случае, если эти операторы перегружаются в тип, который используется в качестве аргумента. В следующим коде иллюстрируется эта особенность; выдается результат false, несмотря на то что класс String используется для перегрузки оператора ==.

public static void OpTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}
static void Main()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpTest<string>(s1, s2);
}

Причиной такого поведения является то, что во время компиляции компилятор знает только, что T является ссылочным типом, и поэтому должен использовать операторы по умолчанию, которые действительны для всех ссылочных типов. Если необходимо проверить равенство значения, рекомендуется также применить ограничение where T : IComparable<T> и реализовать этот интерфейс в любом классе, который используется для создания универсального класса.

Ограничение нескольких параметров

Ограничение может применяться к нескольким параметрам, а несколько ограничений — к одному параметру, как показано в следующем примере:

class Base { }
class Test<T, U>
    where U : struct 
    where T : Base, new() { }

Неограниченные параметры типа

Параметры типа, не имеющие ограничений, например T в открытом классе SampleClass<T>{}, называются неограниченными параметрами типа. Неограниченные параметры типа подчиняются следующим правилам.

  • Операторы != и == не могут использоваться из-за отсутствия гарантии того, что конкретный аргумент типа будет поддерживать эти операторы.

  • Их можно преобразовывать в и из объекта System.Object или явно преобразовывать в любой тип интерфейса.

  • Допускается сравнение со значением NULL. При сравнении неограниченного параметра со значением null сравнение всегда возвращает результат false, если аргумент типа является типом значения.

Параметры типа как ограничения

Использование параметра универсального типа в качестве ограничителя полезно, если функция-член с собственным параметром типа должна ограничивать этот параметр параметром содержащего типа, как показано в следующем примере.

class List<T>
{
    void Add<U>(List<U> items) where U : T {/*...*/}
}

В предыдущем примере T является ограничением типа в контексте метода Add и неограниченным параметром типа в контексте класса List.

Параметры типа также могут использоваться как ограничители в определениях общих классов. Обратите внимание, что ограничение типа должно объявляться в угловых скобках вместе со всеми другими параметрами типа.

//Type parameter V is used as a type constraint. 
public class SampleClass<T, U, V> where T : V { }

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

См. также

Ссылки

Введение в универсальные шаблоны. (Руководство по программированию на C#)

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

Ограничение new (Справочник по C#)

System.Collections.Generic

Основные понятия

Руководство по программированию на C#