Condividi tramite


Interfacce generiche (Guida per programmatori C#)

Spesso è utile definire le interfacce per classi di raccolta generiche o per le classi generiche che rappresentano elementi nella raccolta. Per evitare operazioni di conversione boxing e unboxing sui tipi valore, è preferibile usare le interfacce generiche, ad esempio IComparable<T>, nelle classi generiche. La libreria di classi .NET definisce diverse interfacce generiche da usare con le classi di raccolta nello spazio dei nomi System.Collections.Generic. Per altre informazioni su queste interfacce, vedere Interfacce generiche.

Quando un'interfaccia viene specificata come vincolo o parametro di tipo, è possibile usare solo i tipi che implementano l'interfaccia. L'esempio di codice seguente mostra una classe SortedList<T> che deriva dalla classe GenericList<T>. Per altre informazioni, vedere Introduzione ai generics. SortedList<T> aggiunge il vincolo where T : IComparable<T>. Questo vincolo abilita il metodo BubbleSort in SortedList<T> a usare il metodo CompareTo generico negli elementi dell'elenco. In questo esempio 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 IComparable<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);
    }
}

public class Program
{
    public 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 =
        [
            "Franscoise",
            "Bill",
            "Li",
            "Sandra",
            "Gunnar",
            "Alok",
            "Hiroyuki",
            "Maria",
            "Alessandro",
            "Raul"
        ];

        int[] ages = [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 unico tipo, in questo modo:

class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}

Un'interfaccia può definire più parametri di tipo, in questo modo:

interface IDictionary<K, V>
{
}

Le regole di ereditarietà valide per le classi si applicano anche alle interfacce:

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 è covariante, ovvero usa solo il proprio parametro di tipo come valore restituito. Nella libreria di classi .NET, IEnumerable<T> eredita da IEnumerable perché IEnumerable<T> usa solo T nel valore restituito di GetEnumerator e nel getter proprietà Current.

Le classi concrete possono implementare interfacce costruite chiuse, in questo modo:

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

Le classi generiche possono implementare interfacce generiche o interfacce costruite chiuse purché l'elenco di parametri delle classi specifichi tutti gli argomenti necessari per l'interfaccia, in questo modo:

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 le stesse per i metodi all'interno di classi generiche, struct generici o interfacce generiche. Per altre informazioni, vedere Metodi generici.

A partire da C# 11, le interfacce possono dichiarare i membri static abstract o static virtual. Le interfacce che dichiarano i membri static abstract o static virtual, sono quasi sempre interfacce generiche. Il compilatore deve risolvere le chiamate ai metodi static virtual e static abstract in fase di compilazione. I metodi static virtual e static abstract dichiarati nelle interfacce non hanno un meccanismo di invio di runtime analogo ai metodi virtual o abstract dichiarati nelle classi. Il compilatore usa invece le informazioni sul tipo disponibili in fase di compilazione. In genere questi membri vengono dichiarati nelle interfacce generiche. Inoltre, la maggior parte delle interfacce che dichiarano metodi static virtual o static abstract, dichiarano che uno dei parametri di tipo deve implementare l'interfaccia dichiarata. Il compilatore quindi usa gli argomenti tipo forniti per risolvere il tipo del membro dichiarato.

Vedi anche