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


Ограничения параметров типа (Руководство по программированию на 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#