Interfacce generiche (Guida per programmatori C#)
Risulta spesso utile definire interfacce per classi di insiemi generici o per le classi generiche che rappresentano gli elementi dell'insieme. Con le classi generiche è preferibile utilizzare interfacce generiche, ad esempio IComparable<T>, anziché IComparable, per evitare operazioni di boxing e unboxing sui tipi valore. Nella libreria di classi .NET Framework vengono definite numerose interfacce generiche da utilizzare con le classi di insiemi nello spazio dei nomi System.Collections.Generic.
Quando un'interfaccia viene specificata come vincolo su un parametro di tipo, è possibile utilizzare esclusivamente tipi che implementano l'interfaccia. Nell'esempio di codice riportato di seguito viene illustrata una classe SortedList<T> che deriva dalla classe GenericList<T>. Per ulteriori informazioni, vedere Introduzione ai generics (Guida per programmatori C#). SortedList<T> aggiunge il vincolo where T : IComparable<T>. In questo modo, il metodo BubbleSort in SortedList<T> utilizzerà il metodo CompareTo generico su elementi dell'elenco. Nell'esempio riportato di seguito, gli elementi dell'elenco sono una classe semplice, Person, che implementa IComparable<Person>.
//Type parameter T in angle brackets.
public class GenericList<T> : System.Collections.Generic.IEnumerable<T>
{
protected Node head;
protected Node current = null;
// Nested class is also generic on T
protected class Node
{
public Node next;
private T data; //T as private member datatype
public Node(T t) //T used in non-generic constructor
{
next = null;
data = t;
}
public Node Next
{
get { return next; }
set { next = value; }
}
public T Data //T as return type of property
{
get { return data; }
set { data = value; }
}
}
public GenericList() //constructor
{
head = null;
}
public void AddHead(T t) //T as method parameter type
{
Node n = new Node(t);
n.Next = head;
head = n;
}
// Implementation of the iterator
public System.Collections.Generic.IEnumerator<T> GetEnumerator()
{
Node current = head;
while (current != null)
{
yield return current.Data;
current = current.Next;
}
}
// IEnumerable<T> inherits from IEnumerable, therefore this class
// must implement both the generic and non-generic versions of
// GetEnumerator. In most cases, the non-generic method can
// simply call the generic method.
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public class SortedList<T> : GenericList<T> where T : System.IComparable<T>
{
// A simple, unoptimized sort algorithm that
// orders list elements from lowest to highest:
public void BubbleSort()
{
if (null == head || null == head.Next)
{
return;
}
bool swapped;
do
{
Node previous = null;
Node current = head;
swapped = false;
while (current.next != null)
{
// Because we need to call this method, the SortedList
// class is constrained on IEnumerable<T>
if (current.Data.CompareTo(current.next.Data) > 0)
{
Node tmp = current.next;
current.next = current.next.next;
tmp.next = current;
if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
current = current.next;
}
}
} while (swapped);
}
}
// A simple class that implements IComparable<T> using itself as the
// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable<Person>
{
string name;
int age;
public Person(string s, int i)
{
name = s;
age = i;
}
// This will cause list elements to be sorted on age values.
public int CompareTo(Person p)
{
return age - p.age;
}
public override string ToString()
{
return name + ":" + age;
}
// Must implement Equals.
public bool Equals(Person p)
{
return (this.age == p.age);
}
}
class Program
{
static void Main()
{
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();
//Create name and age values to initialize Person objects.
string[] names = new string[]
{
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};
int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
//Populate the list.
for (int x = 0; x < 10; x++)
{
list.AddHead(new Person(names[x], ages[x]));
}
//Print out unsorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with unsorted list");
//Sort the list.
list.BubbleSort();
//Print out sorted list.
foreach (Person p in list)
{
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with sorted list");
}
}
È possibile specificare più interfacce come vincoli su un solo tipo, come riportato di seguito:
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}
Un'interfaccia può definire più parametri di tipo, come riportato di seguito:
interface IDictionary<K, V>
{
}
Le regole di ereditarietà valgono sia per le interfacce che per le classi:
interface IMonth<T> { }
interface IJanuary : IMonth<int> { } //No error
interface IFebruary<T> : IMonth<int> { } //No error
interface IMarch<T> : IMonth<T> { } //No error
//interface IApril<T> : IMonth<T, U> {} //Error
Le interfacce generiche possono ereditare da interfacce non generiche se l'interfaccia generica è controvariante, ovvero se utilizza esclusivamente il relativo parametro di tipo come valore restituito. Nella libreria di classi .NET Framework IEnumerable<T> eredita da IEnumerable poiché IEnumerable<T> utilizza esclusivamente T nel valore restituito di GetEnumerator e nella proprietà Get Current.
Le classi concrete possono implementare interfacce costruite chiuse, come riportato di seguito:
interface IBaseInterface<T> { }
class SampleClass : IBaseInterface<string> { }
Le classi generiche possono implementare interfacce generiche oppure interfacce costruite chiuse purché l'elenco di parametri di classi fornisca tutti gli argomenti necessari all'interfaccia, come riportato di seguito:
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
class SampleClass1<T> : IBaseInterface1<T> { } //No error
class SampleClass2<T> : IBaseInterface2<T, string> { } //No error
Le regole che controllano l'overload dei metodi sono identiche per i metodi all'interno di classi generiche, strutture generiche o interfacce generiche. Per ulteriori informazioni, vedere Metodi generici (Guida per programmatori C#).
Vedere anche
Riferimenti
Introduzione ai generics (Guida per programmatori C#)
interface (Riferimenti per C#)