Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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 rozpakowywania typów wartościowych, lepiej jest używać interfejsów generycznych, takich jak IComparable<T>, w klasach generycznych. 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 generycznych.
SortedList<T> dodaje ograniczenie where T : IComparable<T>. To ograniczenie umożliwia zastosowanie metody BubbleSort na elementach listy przez metodę SortedList<T> w CompareTo. W tym przykładzie elementy listy to prosta klasa Person, która implementuje 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");
}
}
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. W bibliotece klas platformy .NET, IEnumerable<T> dziedziczy z klasy IEnumerable. Gdy interfejs ogólny dziedziczy z interfejsu innego niż ogólny, parametr typu zazwyczaj zastępuje object w zastąpionych funkcjach członkowskich. Na przykład IEnumerable<T> używa T zamiast object w wartości zwracanej GetEnumerator oraz w selektorze właściwości Current. Ponieważ T jest używany tylko w pozycjach wyjściowych w tych członkach, IEnumerable<T> można oznaczyć jako kowariancyjne. Jeśli T byłoby użyte w pozycji wejściowej w zastąpionym członku, interfejs nie mógłby być kowariantny, a kompilator wygenerowałby błąd.
Klasy konkretne mogą implementować zamknięte konstrukcyjne 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.
Interfejsy mogą deklarować static abstract lub static virtual członków. Interfejsy, które deklarują członków static abstract lub static virtual, 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 dystrybucji w czasie wykonywania podobnego do metod virtual lub abstract zadeklarowanych w klasach. Zamiast tego kompilator używa informacji o typie dostępnych w czasie kompilacji. Te składniki są zwykle deklarowane w interfejsach generycznych. Ponadto większość interfejsów, które deklarują metody lub deklarująstatic virtual, że jeden z parametrów typu musi implementować static abstract. Kompilator używa następnie podanych argumentów typu, aby rozpoznać typ zadeklarowanego elementu członkowskiego.