Vincoli sui parametri di tipo (Guida per programmatori C#)
I vincoli indicano al compilatore quali funzionalità deve usare un argomento tipo. Senza i vincoli, l'argomento tipo può essere qualsiasi tipo. Il compilatore è in grado di dedurre solo i membri di System.Object, che è la principale classe di base per qualsiasi tipo .NET. Per altre informazioni, vedere Motivi per cui usare i vincoli. Se il codice client usa un tipo che non soddisfa un vincolo, il compilatore genera un errore. I vincoli vengono specificati usando la parola chiave contestuale where
. Nella tabella seguente sono elencati i vari tipi di vincoli:
Vincolo | Descrizione |
---|---|
where T : struct |
L'argomento type deve essere un tipo valore non nullable, che include record struct tipi. Per informazioni sui tipi valore nullable, vedere Tipi valore nullable. Poiché tutti i tipi valore hanno un costruttore senza parametri accessibile, dichiarato o implicito, il struct vincolo implica il new() vincolo e non può essere combinato con il new() vincolo. Non è possibile combinare il struct vincolo con il unmanaged vincolo . |
where T : class |
L'argomento tipo deve essere un tipo riferimento. Questo vincolo si applica anche a qualsiasi tipo di classe, interfaccia, delegato o matrice. In un contesto nullable deve T essere un tipo riferimento non nullable. |
where T : class? |
L'argomento di tipo deve essere un tipo riferimento, nullable o non nullable. Questo vincolo si applica anche a qualsiasi classe, interfaccia, delegato o tipo di matrice, inclusi i record. |
where T : notnull |
L'argomento type deve essere un tipo non nullable. L'argomento può essere un tipo riferimento non nullable o un tipo valore non nullable. |
where T : unmanaged |
L'argomento type deve essere un tipo non nullable non gestito. Il unmanaged vincolo implica il struct vincolo e non può essere combinato con i struct vincoli o new() . |
where T : new() |
L'argomento tipo deve avere un costruttore pubblico senza parametri. Quando il vincolo new() viene usato con altri vincoli, deve essere specificato per ultimo. Il new() vincolo non può essere combinato con i struct vincoli e unmanaged . |
where T : <nome della classe base> |
L'argomento tipo deve corrispondere alla classe di base specificata o derivare da essa. In un contesto nullable deve T essere un tipo riferimento non nullable derivato dalla classe di base specificata. |
where T : <nome> della classe di base? |
L'argomento tipo deve corrispondere alla classe di base specificata o derivare da essa. In un contesto T nullable può essere un tipo nullable o non nullable derivato dalla classe di base specificata. |
where T : <nome dell'interfaccia> |
L'argomento tipo deve corrispondere all'interfaccia specificata o implementare tale interfaccia. È possibile specificare più vincoli di interfaccia. L'interfaccia vincolante può anche essere generica. In un contesto nullable deve T essere un tipo non nullable che implementa l'interfaccia specificata. |
where T : <nome> dell'interfaccia? |
L'argomento tipo deve corrispondere all'interfaccia specificata o implementare tale interfaccia. È possibile specificare più vincoli di interfaccia. L'interfaccia vincolante può anche essere generica. In un contesto T nullable può essere un tipo riferimento nullable, un tipo riferimento non nullable o un tipo valore. T non può essere un tipo di valore nullable. |
where T : U |
L'argomento di tipo fornito per T deve essere o derivare dall'argomento fornito per U . In un contesto nullable, se U è un tipo riferimento non nullable, T deve essere un tipo riferimento non nullable. Se U è un tipo riferimento nullable, T può essere nullable o non nullable. |
where T : default |
Questo vincolo risolve l'ambiguità quando è necessario specificare un parametro di tipo non vincolato quando si esegue l'override di un metodo o si fornisce un'implementazione esplicita dell'interfaccia. Il default vincolo implica il metodo di base senza il class vincolo o struct . Per altre informazioni, vedere la proposta relativa alle specifiche di default vincolo . |
Alcuni vincoli si escludono a vicenda e alcuni vincoli devono essere in un ordine specificato:
- È possibile applicare al massimo uno dei
struct
vincoli ,class
,class?
,notnull
eunmanaged
. Se si specifica uno di questi vincoli, deve essere il primo vincolo specificato per tale parametro di tipo. - Il vincolo della classe base (
where T : Base
owhere T : Base?
) non può essere combinato con nessuno dei vincolistruct
, ,class
class?
,notnull
ounmanaged
. - È possibile applicare al massimo un vincolo di classe base, in entrambi i moduli. Se si vuole supportare il tipo di base nullable, usare
Base?
. - Non è possibile assegnare un nome sia alla forma non nullable che nullable di un'interfaccia come vincolo.
- Il vincolo
new()
non può essere combinato con il vincolostruct
ounmanaged
. Se si specifica ilnew()
vincolo, deve essere l'ultimo vincolo per il parametro di tipo. - Il
default
vincolo può essere applicato solo alle implementazioni dell'interfaccia di override o esplicite. Non può essere combinato con istruct
vincoli oclass
.
Motivi per cui usare i vincoli
I vincoli specificano le funzionalità e le aspettative di un parametro di tipo. La dichiarazione di tali vincoli significa che è possibile usare le operazioni e le chiamate al metodo del tipo di vincolo. Si applicano vincoli al parametro di tipo quando la classe o il metodo generico usa qualsiasi operazione sui membri generici oltre all'assegnazione semplice, che include la chiamata di qualsiasi metodo non supportato da System.Object. Ad esempio, il vincolo della classe base indica al compilatore che solo gli oggetti di questo tipo o derivati da questo tipo possono sostituire tale argomento di tipo. In presenza di questa garanzia, il compilatore può consentire le chiamate ai metodi del tipo all'interno della classe generica. 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.
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
. Il vincolo specifica che tutti gli elementi di tipo T
sono sicuramente un oggetto Employee
o un oggetto che eredita da Employee
.
È possibile applicare più vincoli allo stesso parametro di tipo. I vincoli stessi possono essere tipi generici, come illustrato di seguito:
class EmployeeList<T> where T : Employee, System.Collections.Generic.IList<T>, IDisposable, new()
{
// ...
}
Quando si applica il where T : class
vincolo, evitare gli ==
operatori e !=
nel parametro di tipo perché questi operatori testano solo l'identità di riferimento, non per l'uguaglianza dei valori. Questo comportamento si verifica anche se si esegue l'overload degli operatori in un tipo usato come argomento. Il codice seguente illustra questo aspetto. L'output è false anche se la classe String esegue l'overload dell'operatore ==
.
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 riferimento in fase di compilazione e deve usare gli operatori predefiniti validi per tutti i tipi di riferimento. Se è necessario verificare l'uguaglianza dei valori, applicare il where T : IEquatable<T>
vincolo o where T : IComparable<T>
e implementare l'interfaccia in qualsiasi classe usata per costruire la classe generica.
Vincolo di più parametri
È possibile applicare vincoli a più parametri e più vincoli a un singolo parametro, come illustrato nell'esempio seguente:
class Base { }
class Test<T, U>
where U : struct
where T : Base, new()
{ }
Parametri di tipo senza vincoli
I parametri di tipo che non hanno vincoli, ad esempio T nella classe pubblica SampleClass<T>{}
, sono detti parametri di tipo senza vincoli. I parametri di tipo senza vincoli prevedono le regole seguenti:
- Gli
!=
operatori e==
non possono essere usati perché non esiste alcuna garanzia che l'argomento del tipo concreto supporti questi operatori. - Possono essere convertiti in e da
System.Object
oppure convertiti in modo esplicito in qualsiasi tipo di interfaccia. - È possibile confrontarli con Null. Se un parametro non associato viene confrontato con
null
, il confronto restituisce sempre false se l'argomento di tipo è un tipo valore.
Parametri di tipo come vincoli
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:
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
.
I parametri di tipo possono anche essere usati come vincoli nelle definizioni di classi generiche. Il parametro di tipo deve essere dichiarato tra parentesi acute, insieme a eventuali altri parametri di tipo:
//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
. 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.
notnull
Vincolo
È possibile utilizzare il notnull
vincolo per specificare che l'argomento di tipo deve essere un tipo di valore non nullable o un tipo riferimento non nullable. A differenza della maggior parte degli altri vincoli, se un argomento di tipo viola il notnull
vincolo, il compilatore genera un avviso anziché un errore.
Il notnull
vincolo ha un effetto solo quando viene usato in un contesto nullable. Se si aggiunge il notnull
vincolo in un contesto oblivious nullable, il compilatore non genera avvisi o errori per violazioni del vincolo.
class
Vincolo
Il class
vincolo in un contesto nullable specifica che l'argomento di tipo deve essere un tipo riferimento non nullable. In un contesto nullable, quando un argomento di tipo è un tipo riferimento nullable, il compilatore genera un avviso.
default
Vincolo
L'aggiunta di tipi riferimento nullable complica l'uso di T?
in un tipo o metodo generico. T?
può essere usato con il struct
vincolo o class
, ma uno di essi deve essere presente. Quando è stato usato il class
vincolo, T?
viene fatto riferimento al tipo riferimento nullable per T
. T?
può essere usato quando non viene applicato alcun vincolo. In tal caso, T?
viene interpretato come T?
per i tipi valore e i tipi riferimento. Tuttavia, se T
è un'istanza di Nullable<T>, T?
è uguale T
a . In altre parole, non diventa T??
.
Poiché T?
ora è possibile usare senza il class
vincolo o struct
, le ambiguità possono verificarsi in override o implementazioni esplicite dell'interfaccia. In entrambi i casi, l'override non include i vincoli, ma li eredita dalla classe di base. Quando la classe base non applica né il class
vincolo o struct
, le classi derivate devono in qualche modo specificare un override si applica al metodo di base senza alcun vincolo. Il metodo derivato applica il default
vincolo . Il default
vincolo non chiarisce né il class
vincolo né struct
.
Vincolo non gestito
È possibile usare il unmanaged
vincolo per specificare che il parametro di tipo deve essere un tipo non nullable non gestito. 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:
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. Senza il vincolo unmanaged
l'operatore sizeof
non è disponibile.
Il unmanaged
vincolo implica il struct
vincolo e non può essere combinato con esso. Poiché il struct
vincolo implica il new()
vincolo, il unmanaged
vincolo non può essere combinato anche con il new()
vincolo.
Vincoli dei delegati
È possibile usare System.Delegate o System.MulticastDelegate come vincolo di classe di base. Il supporto Common Language Runtime (CLR) consente sempre questo vincolo, a differenza del linguaggio C#. Il vincolo System.Delegate
consente di scrivere codice che funziona con i delegati in modo indipendente dai tipi. Il codice seguente definisce un metodo di estensione che combina due delegati purché siano dello stesso tipo:
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:
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. Sia first
che test
sono tipi delegati, ma sono tipi delegati diversi.
Vincoli di enumerazione
È anche possibile specificare il System.Enum tipo come vincolo di classe base. Il supporto Common Language Runtime (CLR) consente sempre questo vincolo, a differenza del linguaggio C#. 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
. 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.
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
usare la reflection, che ha implicazioni sulle prestazioni. È possibile chiamare EnumNamedValues
per compilare una raccolta memorizzata nella cache e riutilizzata anziché ripetere le chiamate che richiedono la reflection.
Il metodo può essere usato come illustrato nell'esempio seguente per creare un'enumerazione e compilare un dizionario dei relativi valori e nomi:
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}");
Gli argomenti di tipo implementano l'interfaccia dichiarata
Alcuni scenari richiedono che un argomento fornito per un parametro di tipo implementi tale interfaccia. Ad esempio:
public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{
public abstract static T operator +(T left, T right);
public abstract static T operator -(T left, T right);
}
Questo modello consente al compilatore C# di determinare il tipo contenitore per gli operatori di overload o qualsiasi static virtual
metodo o static abstract
. Fornisce la sintassi in modo che gli operatori di addizione e sottrazione possano essere definiti in un tipo contenitore. Senza questo vincolo, i parametri e gli argomenti devono essere dichiarati come interfaccia, anziché come parametro di tipo:
public interface IAdditionSubtraction<T> where T : IAdditionSubtraction<T>
{
public abstract static IAdditionSubtraction<T> operator +(
IAdditionSubtraction<T> left,
IAdditionSubtraction<T> right);
public abstract static IAdditionSubtraction<T> operator -(
IAdditionSubtraction<T> left,
IAdditionSubtraction<T> right);
}
La sintassi precedente richiederebbe agli implementatori di usare l'implementazione esplicita dell'interfaccia per tali metodi. Se si specifica il vincolo aggiuntivo, l'interfaccia consente di definire gli operatori in termini di parametri di tipo. I tipi che implementano l'interfaccia possono implementare in modo implicito i metodi di interfaccia.
Vedi anche
Commenti e suggerimenti
https://aka.ms/ContentUserFeedback.
Presto disponibile: Nel corso del 2024 verranno gradualmente disattivati i problemi di GitHub come meccanismo di feedback per il contenuto e ciò verrà sostituito con un nuovo sistema di feedback. Per altre informazioni, vedereInvia e visualizza il feedback per