Interfejsy ogólne (Przewodnik programowania w języku C#)
Często przydatne jest zdefiniowanie interfejsów dla klas kolekcji ogólnych lub dla klas ogólnych reprezentujących elementy w kolekcji. Aby uniknąć operacji boksowania i rozpboxowania dla typów wartości, lepiej jest używać interfejsów ogólnych, takich jak IComparable<T>, w klasach ogólnych. Biblioteka klas platformy .NET definiuje kilka ogólnych interfejsów do użycia z klasami kolekcji w System.Collections.Generic przestrzeni nazw. Aby uzyskać więcej informacji na temat tych interfejsów, zobacz Interfejsy ogólne.
Jeśli interfejs jest określony jako ograniczenie dla parametru typu, można używać tylko typów, które implementują interfejs. Poniższy przykład kodu przedstawia klasę pochodzącą SortedList<T>
z GenericList<T>
klasy . Aby uzyskać więcej informacji, zobacz Wprowadzenie do typów ogólnych. SortedList<T>
dodaje ograniczenie where T : IComparable<T>
. To ograniczenie umożliwia metodę BubbleSort
w metodzie w SortedList<T>
celu użycia metody ogólnej CompareTo w elementach listy. W tym przykładzie elementy listy są prostą klasą, Person
która implementuje IComparable<Person>
element .
//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");
}
}
Wiele interfejsów można określić jako ograniczenia dla jednego typu w następujący sposób:
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}
Interfejs może zdefiniować więcej niż jeden parametr typu w następujący sposób:
interface IDictionary<K, V>
{
}
Reguły dziedziczenia, które mają zastosowanie do klas, mają również zastosowanie do interfejsów:
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
Interfejsy ogólne mogą dziedziczyć z interfejsów innych niż ogólne, jeśli interfejs ogólny jest kowariantny, co oznacza, że używa tylko parametru typu jako wartości zwracanej. W bibliotece IEnumerable<T> klas platformy .NET dziedziczy z IEnumerable , ponieważ IEnumerable<T> używa T
tylko wartości zwracanej GetEnumerator wartości i w metodzie Current getter właściwości.
Klasy betonowe mogą implementować zamknięte skonstruowane interfejsy w następujący sposób:
interface IBaseInterface<T> { }
class SampleClass : IBaseInterface<string> { }
Klasy ogólne mogą implementować interfejsy ogólne lub zamknięte skonstruowane interfejsy, o ile lista parametrów klasy dostarcza wszystkie argumenty wymagane przez interfejs w następujący sposób:
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
class SampleClass1<T> : IBaseInterface1<T> { } //No error
class SampleClass2<T> : IBaseInterface2<T, string> { } //No error
Reguły, które sterują przeciążeniem metody, są takie same dla metod w klasach ogólnych, strukturach ogólnych lub interfejsach ogólnych. Aby uzyskać więcej informacji, zobacz Metody ogólne.
Począwszy od języka C# 11, interfejsy mogą deklarować static abstract
lub static virtual
składowe. Interfejsy, które deklarują albo static abstract
static virtual
elementy członkowskie, są prawie zawsze interfejsami ogólnymi. Kompilator musi rozpoznawać wywołania metod static virtual
i w static abstract
czasie kompilacji. static virtual
i static abstract
metody zadeklarowane w interfejsach nie mają mechanizmu wysyłania w czasie wykonywania analogiczny do virtual
lub abstract
metod zadeklarowanych w klasach. Zamiast tego kompilator używa informacji o typie dostępnych w czasie kompilacji. Te elementy członkowskie są zwykle deklarowane w interfejsach ogólnych. Ponadto większość interfejsów, które deklarują metody lub deklarująstatic virtual
, że jeden z parametrów typu musi implementować zadeklarowany interfejs.static abstract
Kompilator używa następnie podanych argumentów typu, aby rozpoznać typ zadeklarowanego elementu członkowskiego.