Vincoli sui parametri di tipo (Guida per programmatori C#)Constraints on type parameters (C# Programming Guide)

I vincoli indicano al compilatore quali funzionalità deve usare un argomento tipo.Constraints inform the compiler about the capabilities a type argument must have. Senza i vincoli, l'argomento tipo può essere qualsiasi tipo.Without any constraints, the type argument could be any type. Il compilatore è in grado di dedurre solo i membri di System.Object, che è la principale classe di base per qualsiasi tipo .NET.The compiler can only assume the members of System.Object, which is the ultimate base class for any .NET type. Per altre informazioni, vedere Motivi per cui usare i vincoli.For more information, see Why use constraints. Se il codice client usa un tipo che non soddisfa un vincolo, il compilatore genera un errore.If client code uses a type that doesn't satisfy a constraint, the compiler issues an error. I vincoli vengono specificati usando la parola chiave contestuale where.Constraints are specified by using the where contextual keyword. Nella tabella seguente sono elencati i vari tipi di vincoli:The following table lists the various types of constraints:

VincoloConstraint DescrizioneDescription
where T : struct L'argomento di tipo deve essere un tipo di valorenon nullable.The type argument must be a non-nullable value type. Per informazioni sui tipi di valore Nullable, vedere tipi di valore Nullable.For information about nullable value types, see Nullable value types. Poiché tutti i tipi di valore hanno un costruttore senza parametri accessibile, il struct vincolo implica il new() vincolo e non può essere combinato con il new() vincolo.Because all value types have an accessible parameterless constructor, the struct constraint implies the new() constraint and can't be combined with the new() constraint. Non è possibile combinare il struct vincolo con il unmanaged vincolo.You can't combine the struct constraint with the unmanaged constraint.
where T : class L'argomento tipo deve essere un tipo riferimento.The type argument must be a reference type. Questo vincolo si applica anche a qualsiasi tipo di classe, interfaccia, delegato o matrice.This constraint applies also to any class, interface, delegate, or array type. In un contesto nullable in C# 8,0 o versione successiva T deve essere un tipo di riferimento non nullable.In a nullable context in C# 8.0 or later, T must be a non-nullable reference type.
where T : class? L'argomento di tipo deve essere un tipo riferimento, che ammette i valori null o che non ammette valori null.The type argument must be a reference type, either nullable or non-nullable. Questo vincolo si applica anche a qualsiasi tipo di classe, interfaccia, delegato o matrice.This constraint applies also to any class, interface, delegate, or array type.
where T : notnull L'argomento di tipo deve essere un tipo non nullable.The type argument must be a non-nullable type. L'argomento può essere un tipo di riferimento non nullable in C# 8,0 o versione successiva oppure un tipo di valore non nullable.The argument can be a non-nullable reference type in C# 8.0 or later, or a non-nullable value type.
where T : unmanaged L'argomento di tipo deve essere un tipo non gestitoche non ammette i valori null.The type argument must be a non-nullable unmanaged type. Il unmanaged vincolo implica il struct vincolo e non può essere combinato con i struct new() vincoli o.The unmanaged constraint implies the struct constraint and can't be combined with either the struct or new() constraints.
where T : new() L'argomento tipo deve avere un costruttore pubblico senza parametri.The type argument must have a public parameterless constructor. Quando il vincolo new() viene usato con altri vincoli, deve essere specificato per ultimo.When used together with other constraints, the new() constraint must be specified last. Il new() vincolo non può essere combinato con struct i unmanaged vincoli e.The new() constraint can't be combined with the struct and unmanaged constraints.
where T : <base class name>where T : <base class name> L'argomento tipo deve corrispondere alla classe di base specificata o derivare da essa.The type argument must be or derive from the specified base class. In un contesto nullable in C# 8,0 e versioni successive, T deve essere un tipo di riferimento non nullable derivato dalla classe base specificata.In a nullable context in C# 8.0 and later, T must be a non-nullable reference type derived from the specified base class.
where T : <base class name>?where T : <base class name>? L'argomento tipo deve corrispondere alla classe di base specificata o derivare da essa.The type argument must be or derive from the specified base class. In un contesto nullable in C# 8,0 e versioni successive, T può essere un tipo nullable o non nullable derivato dalla classe base specificata.In a nullable context in C# 8.0 and later, T may be either a nullable or non-nullable type derived from the specified base class.
where T : <interface name>where T : <interface name> L'argomento tipo deve corrispondere all'interfaccia specificata o implementare tale interfaccia.The type argument must be or implement the specified interface. È possibile specificare più vincoli di interfaccia.Multiple interface constraints can be specified. L'interfaccia vincolante può anche essere generica.The constraining interface can also be generic. In un contesto nullable in C# 8,0 e versioni successive, T deve essere un tipo non nullable che implementa l'interfaccia specificata.In a nullable context in C# 8.0 and later, T must be a non-nullable type that implements the specified interface.
where T : <interface name>?where T : <interface name>? L'argomento tipo deve corrispondere all'interfaccia specificata o implementare tale interfaccia.The type argument must be or implement the specified interface. È possibile specificare più vincoli di interfaccia.Multiple interface constraints can be specified. L'interfaccia vincolante può anche essere generica.The constraining interface can also be generic. In un contesto nullable in C# 8,0, T può essere un tipo di riferimento Nullable, un tipo di riferimento non nullable o un tipo di valore.In a nullable context in C# 8.0, T may be a nullable reference type, a non-nullable reference type, or a value type. T non può essere un tipo di valore Nullable.T may not be a nullable value type.
where T : U L'argomento di tipo fornito per T deve essere o derivare dall'argomento fornito per U .The type argument supplied for T must be or derive from the argument supplied for U. In un contesto Nullable, se U è un tipo di riferimento non nullable, T deve essere un tipo di riferimento non nullable.In a nullable context, if U is a non-nullable reference type, T must be non-nullable reference type. Se U è un tipo di riferimento Nullable, T può essere nullable o non nullable.If U is a nullable reference type, T may be either nullable or non-nullable.

Motivi per cui usare i vincoliWhy use constraints

I vincoli specificano le funzionalità e le aspettative di un parametro di tipo.Constraints specify the capabilities and expectations of a type parameter. Dichiarando tali vincoli, è possibile utilizzare le operazioni e le chiamate al metodo del tipo vincolante.Declaring those constraints means you can use the operations and method calls of the constraining type. Se la classe o il metodo generico Usa qualsiasi operazione sui membri generici oltre l'assegnazione semplice o la chiamata a metodi non supportati da System.Object , sarà necessario applicare vincoli al parametro di tipo.If your generic class or method uses any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you'll have to apply constraints to the type parameter. Specificando il vincolo della classe di base, ad esempio, si indica al compilatore che verranno usati come argomenti tipo solo gli oggetti del tipo specificato o derivati da esso.For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. In presenza di questa garanzia, il compilatore può consentire le chiamate ai metodi del tipo all'interno della classe generica.Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. L'esempio di codice seguente illustra la funzionalità che è possibile aggiungere alla classe GenericList<T> (in Introduzione ai generics) applicando un vincolo della classe di base.The following code example demonstrates the functionality you can add to the GenericList<T> class (in Introduction to Generics) by applying a base class constraint.

public class Employee
{
    public Employee(string name, int id) => (Name, ID) = (name, id);
    public string Name { get; set; }
    public int ID { get; set; }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        public Node(T t) => (Next, Data) = (null, t);

        public Node Next { get; set; }
        public T Data { get; set; }
    }

    private Node head;

    public void AddHead(T t)
    {
        Node n = new Node(t) { 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;
    }
}

Il vincolo consente alla classe generica di usare la proprietà Employee.Name.The constraint enables the generic class to use the Employee.Name property. Il vincolo specifica che tutti gli elementi di tipo T sono sicuramente un oggetto Employee o un oggetto che eredita da Employee.The constraint specifies that all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee.

È possibile applicare più vincoli allo stesso parametro di tipo. I vincoli stessi possono essere tipi generici, come illustrato di seguito:Multiple constraints can be applied to the same type parameter, and the constraints themselves can be generic types, as follows:

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

Quando si applica il vincolo where T : class, evitare gli operatori == e != nel parametro di tipo perché questi operatori verificano solo l'identità del riferimento e non l'uguaglianza dei valori.When applying the where T : class constraint, avoid the == and != operators on the type parameter because these operators will test for reference identity only, not for value equality. Questo comportamento si verifica anche se si esegue l'overload degli operatori in un tipo usato come argomento.This behavior occurs even if these operators are overloaded in a type that is used as an argument. Il codice seguente illustra questo aspetto. L'output è false anche se la classe String esegue l'overload dell'operatore ==.The following code illustrates this point; the output is false even though the String class overloads the == operator.

public static void OpEqualsTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}

private static void TestStringEquality()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpEqualsTest<string>(s1, s2);
}

Il compilatore sa solo che T è un tipo di riferimento in fase di compilazione e deve usare gli operatori predefiniti validi per tutti i tipi di riferimento.The compiler only knows that T is a reference type at compile time and must use the default operators that are valid for all reference types. Per verificare l'uguaglianza dei valori, è consigliabile applicare anche il vincolo where T : IEquatable<T> o where T : IComparable<T> e implementare l'interfaccia nelle classi che verranno usate per costruire la classe generica.If you must test for value equality, the recommended way is to also apply the where T : IEquatable<T> or where T : IComparable<T> constraint and implement the interface in any class that will be used to construct the generic class.

Vincolo di più parametriConstraining multiple parameters

È possibile applicare vincoli a più parametri e più vincoli a un singolo parametro, come illustrato nell'esempio seguente:You can apply constraints to multiple parameters, and multiple constraints to a single parameter, as shown in the following example:

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

Parametri di tipo senza vincoliUnbounded type parameters

I parametri di tipo che non hanno vincoli, ad esempio T nella classe pubblica SampleClass<T>{}, sono detti parametri di tipo senza vincoli.Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. I parametri di tipo senza vincoli prevedono le regole seguenti:Unbounded type parameters have the following rules:

  • Gli != operatori e non == possono essere usati perché non vi è alcuna garanzia che l'argomento di tipo concreto supporterà questi operatori.The != and == operators can't be used because there's no guarantee that the concrete type argument will support these operators.
  • Possono essere convertiti in e da System.Object oppure convertiti in modo esplicito in qualsiasi tipo di interfaccia.They can be converted to and from System.Object or explicitly converted to any interface type.
  • È possibile confrontarli con Null.You can compare them to null. Se si confronta un parametro senza vincoli con null e l'argomento tipo è un tipo valore, verrà sempre restituito false.If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

Parametri di tipo come vincoliType parameters as constraints

L'uso di un parametro di tipo generico come vincolo è utile quando una funzione membro con il proprio parametro di tipo deve vincolare tale parametro a quello del tipo che lo contiene, come illustrato nell'esempio seguente:The use of a generic type parameter as a constraint is useful when a member function with its own type parameter has to constrain that parameter to the type parameter of the containing type, as shown in the following example:

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

Nell'esempio precedente T è un vincolo di tipo nel contesto del metodo Add e un parametro di tipo senza vincoli nel contesto della classe List.In the previous example, T is a type constraint in the context of the Add method, and an unbounded type parameter in the context of the List class.

I parametri di tipo possono anche essere usati come vincoli nelle definizioni di classi generiche.Type parameters can also be used as constraints in generic class definitions. Il parametro di tipo deve essere dichiarato tra parentesi acute, insieme a eventuali altri parametri di tipo:The type parameter must be declared within the angle brackets together with any other type parameters:

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

L'utilità dei parametri di tipo usati come vincoli in classi generiche è limitata poiché il compilatore non può presupporre niente riguardo al parametro di tipo, tranne il fatto che deriva da System.Object.The usefulness of type parameters as constraints with generic classes is limited because the compiler can assume nothing about the type parameter except that it derives from System.Object. Usare i parametri di tipo come vincoli nelle classi generiche in scenari in cui si vuole applicare una relazione di ereditarietà tra due parametri di tipo.Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

Vincolo NotNullNotNull constraint

A partire da C# 8,0 in un contesto Nullable, è possibile usare il notnull vincolo per specificare che l'argomento di tipo deve essere un tipo di valore non nullable o un tipo di riferimento non nullable.Beginning with C# 8.0 in a nullable context, you can use the notnull constraint to specify that the type argument must be a non-nullable value type or non-nullable reference type. Il notnull vincolo può essere usato solo in un nullable enable contesto.The notnull constraint can only be used in a nullable enable context. Il compilatore genera un avviso se si aggiunge il notnull vincolo in un contesto ignaro che ammette i valori null.The compiler generates a warning if you add the notnull constraint in a nullable oblivious context.

Diversamente da altri vincoli, quando un argomento di tipo viola il notnull vincolo, il compilatore genera un avviso quando il codice viene compilato in un nullable enable contesto.Unlike other constraints, when a type argument violates the notnull constraint, the compiler generates a warning when that code is compiled in a nullable enable context. Se il codice viene compilato in un contesto ignaro Nullable, il compilatore non genera avvisi o errori.If the code is compiled in a nullable oblivious context, the compiler doesn't generate any warnings or errors.

A partire da C# 8,0 in un contesto Nullable, il class vincolo specifica che l'argomento di tipo deve essere un tipo di riferimento non nullable.Beginning with C# 8.0 in a nullable context, the class constraint specifies that the type argument must be a non-nullable reference type. In un contesto Nullable, quando un parametro di tipo è un tipo di riferimento Nullable, il compilatore genera un avviso.In a nullable context, when a type parameter is a nullable reference type, the compiler generates a warning.

Vincolo non gestitoUnmanaged constraint

A partire da C# 7,3, è possibile usare il unmanaged vincolo per specificare che il parametro di tipo deve essere un tipo non gestitoche non ammette i valori null.Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be a non-nullable unmanaged type. Il vincolo unmanaged consente di scrivere routine riutilizzabili per lavorare con tipi che possono essere modificati come blocchi di memoria, come illustrato nell'esempio seguente:The unmanaged constraint enables you to write reusable routines to work with types that can be manipulated as blocks of memory, as shown in the following example:

unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
    var size = sizeof(T);
    var result = new Byte[size];
    Byte* p = (byte*)&argument;
    for (var i = 0; i < size; i++)
        result[i] = *p++;
    return result;
}

Il metodo precedente deve essere compilato in un contesto unsafe perché usa l'operatore sizeof per un tipo non noto come tipo predefinito.The preceding method must be compiled in an unsafe context because it uses the sizeof operator on a type not known to be a built-in type. Senza il vincolo unmanaged l'operatore sizeof non è disponibile.Without the unmanaged constraint, the sizeof operator is unavailable.

Il unmanaged vincolo implica il struct vincolo e non può essere combinato con esso.The unmanaged constraint implies the struct constraint and can't be combined with it. Poiché il vincolo struct implica il new() vincolo, il unmanaged vincolo non può essere combinato anche con il vincolo new() .Because the struct constraint implies the new() constraint, the unmanaged constraint can't be combined with the new() constraint as well.

Vincoli dei delegatiDelegate constraints

A partire da C# 7.3 è inoltre possibile usare System.Delegate o System.MulticastDelegate come vincolo di classe di base.Also beginning with C# 7.3, you can use System.Delegate or System.MulticastDelegate as a base class constraint. Il supporto Common Language Runtime (CLR) consente sempre questo vincolo, a differenza del linguaggio C#.The CLR always allowed this constraint, but the C# language disallowed it. Il vincolo System.Delegate consente di scrivere codice che funziona con i delegati in modo indipendente dai tipi.The System.Delegate constraint enables you to write code that works with delegates in a type-safe manner. Il codice seguente definisce un metodo di estensione che combina due delegati purché siano dello stesso tipo:The following code defines an extension method that combines two delegates provided they're the same type:

public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : System.Delegate
    => Delegate.Combine(source, target) as TDelegate;

Per combinare delegati dello stesso tipo, è possibile usare il metodo riportato sopra:You can use the above method to combine delegates that are the same type:

Action first = () => Console.WriteLine("this");
Action second = () => Console.WriteLine("that");

var combined = first.TypeSafeCombine(second);
combined();

Func<bool> test = () => true;
// Combine signature ensures combined delegates must
// have the same type.
//var badCombined = first.TypeSafeCombine(test);

Se si rimuove il commento dall'ultima riga, non verrà compilata.If you uncomment the last line, it won't compile. Sia first che test sono tipi delegati, ma si tratta di tipi delegati diversi.Both first and test are delegate types, but they're different delegate types.

Vincoli di enumerazioneEnum constraints

A partire da C# 7.3 è anche possibile specificare il tipo System.Enum come vincolo di classe di base.Beginning in C# 7.3, you can also specify the System.Enum type as a base class constraint. Il supporto Common Language Runtime (CLR) consente sempre questo vincolo, a differenza del linguaggio C#.The CLR always allowed this constraint, but the C# language disallowed it. I generics che usano System.Enum offrono una programmazione indipendente dai tipi che consente di memorizzare nella cache i risultati dei metodi statici in System.Enum.Generics using System.Enum provide type-safe programming to cache results from using the static methods in System.Enum. Nell'esempio seguente vengono individuati tutti i valori validi per un tipo di enumerazione e viene compilato un dizionario che esegue il mapping di tali valori alla propria rappresentazione di stringa.The following sample finds all the valid values for an enum type, and then builds a dictionary that maps those values to its string representation.

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Enum.GetValues e Enum.GetName usano la reflection, che ha implicazioni sulle prestazioni.Enum.GetValues and Enum.GetName use reflection, which has performance implications. È possibile chiamare EnumNamedValues per compilare una raccolta memorizzata nella cache e riutilizzata anziché ripetere le chiamate che richiedono la reflection.You can call EnumNamedValues to build a collection that is cached and reused rather than repeating the calls that require reflection.

Il metodo può essere usato come illustrato nell'esempio seguente per creare un'enumerazione e compilare un dizionario dei relativi valori e nomi:You could use it as shown in the following sample to create an enum and build a dictionary of its values and names:

enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
var map = EnumNamedValues<Rainbow>();

foreach (var pair in map)
    Console.WriteLine($"{pair.Key}:\t{pair.Value}");

Vedi ancheSee also