Share via


Allmänna gränssnitt (programmeringsguide för C#)

Det är ofta användbart att definiera gränssnitt antingen för generiska samlingsklasser eller för de generiska klasser som representerar objekt i samlingen. För att undvika boxnings- och avboxningsåtgärder för värdetyper är det bättre att använda generiska gränssnitt, till exempel IComparable<T>, i allmänna klasser. .NET-klassbiblioteket definierar flera allmänna gränssnitt för användning med samlingsklasserna i System.Collections.Generic namnområdet. Mer information om dessa gränssnitt finns i Allmänna gränssnitt.

När ett gränssnitt anges som en begränsning för en typparameter kan endast typer som implementerar gränssnittet användas. I följande kodexempel visas en SortedList<T> klass som härleds från GenericList<T> klassen. Mer information finns i Introduktion till generiska objekt. SortedList<T> lägger till villkoret where T : IComparable<T>. Med den här begränsningen BubbleSort kan metoden i SortedList<T> använda den generiska CompareTo metoden för listelement. I det här exemplet är listelement en enkel klass som Person implementerar 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");
    }
}

Flera gränssnitt kan anges som begränsningar för en enda typ, enligt följande:

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

Ett gränssnitt kan definiera fler än en typparameter enligt följande:

interface IDictionary<K, V>
{
}

Arvsregler som gäller för klasser gäller även för gränssnitt:

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

Generiska gränssnitt kan ärva från icke-generiska gränssnitt om det generiska gränssnittet är covariant, vilket innebär att det bara använder sin typparameter som ett returvärde. I .NET-klassbiblioteket IEnumerable<T> ärver du från IEnumerable eftersom IEnumerable<T> endast använder T i returvärdet GetEnumerator för och i Current egenskaps getter.

Betongklasser kan implementera stängda konstruerade gränssnitt på följande sätt:

interface IBaseInterface<T> { }

class SampleClass : IBaseInterface<string> { }

Generiska klasser kan implementera generiska gränssnitt eller stängda konstruerade gränssnitt så länge klassparameterlistan innehåller alla argument som krävs av gränssnittet, enligt följande:

interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }

class SampleClass1<T> : IBaseInterface1<T> { }          //No error
class SampleClass2<T> : IBaseInterface2<T, string> { }  //No error

Reglerna som styr överlagring av metoder är desamma för metoder inom generiska klasser, generiska structs eller generiska gränssnitt. Mer information finns i Allmänna metoder.

Från och med C# 11 kan gränssnitt deklarera static abstract eller static virtual medlemmar. Gränssnitt som deklarerar antingen static abstract eller static virtual medlemmar är nästan alltid allmänna gränssnitt. Kompilatorn måste matcha anrop till static virtual och static abstract metoder vid kompileringstillfället. static virtual och static abstract metoder som deklareras i gränssnitt har inte någon körningssändningsmekanism som motsvarar virtual eller abstract metoder som deklarerats i klasser. Kompilatorn använder i stället typinformation som är tillgänglig vid kompileringstillfället. Dessa medlemmar deklareras vanligtvis i allmänna gränssnitt. Dessutom deklarerar de flesta gränssnitt som deklarerar static virtual eller static abstract metoder att en av typparametrarna måste implementera det deklarerade gränssnittet. Kompilatorn använder sedan de angivna typargumenten för att matcha den deklarerade medlemmens typ.

Se även