Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Wszystkie metody oparte na LINQ są zgodne z jednym z dwóch podobnych wzorców. Przyjmują sekwencję wyliczalną. Zwracają inną sekwencję lub pojedynczą wartość. Spójność kształtu umożliwia rozszerzenie LINQ przez pisanie metod o podobnym kształcie. W rzeczywistości biblioteki .NET zyskały nowe metody w wielu wersjach platformy .NET od czasu wprowadzenia linQ. W tym artykule przedstawiono przykłady rozszerzania LINQ przez napisanie własnych metod, które są zgodne z tym samym wzorcem.
Dodawanie niestandardowych metod dla zapytań LINQ
Rozszerzysz zestaw metod używanych dla zapytań LINQ, dodając metody rozszerzenia do interfejsu IEnumerable<T> . Na przykład oprócz standardowej średniej lub maksymalnej operacji można utworzyć niestandardową metodę agregacji, aby obliczyć pojedynczą wartość z sekwencji wartości. Utworzysz również metodę, która działa jako filtr niestandardowy lub konkretna transformacja danych dla sekwencji wartości i zwraca nową sekwencję. Przykłady takich metod to Distinct, Skipi Reverse.
Po rozszerzeniu interfejsu IEnumerable<T> można zastosować metody niestandardowe do dowolnej kolekcji z możliwością wyliczania. Aby uzyskać więcej informacji, zobacz Metody rozszerzeń.
Metoda agregacji oblicza pojedynczą wartość z zestawu wartości. LINQ udostępnia kilka metod agregacji, w tym Average, Mini Max. Możesz utworzyć własną metodę agregacji, dodając metodę rozszerzenia do interfejsu IEnumerable<T> .
Począwszy od języka C# 14, można zadeklarować blok rozszerzenia zawierający wiele elementów członkowskich rozszerzenia. Blok rozszerzenia należy zadeklarować za pomocą słowa kluczowego extension
, a następnie parametru odbiornika w nawiasach. Poniższy przykład kodu pokazuje, jak utworzyć metodę rozszerzenia o nazwie Median
w bloku rozszerzenia. Metoda oblicza medianę dla sekwencji liczb typu double
.
extension(IEnumerable<double>? source)
{
public double Median()
{
if (source is null || !source.Any())
{
throw new InvalidOperationException("Cannot compute median for a null or empty set.");
}
var sortedList =
source.OrderBy(number => number).ToList();
int itemIndex = sortedList.Count / 2;
if (sortedList.Count % 2 == 0)
{
// Even number of items.
return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
}
else
{
// Odd number of items.
return sortedList[itemIndex];
}
}
}
Modyfikator można również dodać this
do metody statycznej, aby zadeklarować metodę rozszerzenia. Poniższy kod przedstawia równoważną Median
metodę rozszerzenia:
public static class EnumerableExtension
{
public static double Median(this IEnumerable<double>? source)
{
if (source is null || !source.Any())
{
throw new InvalidOperationException("Cannot compute median for a null or empty set.");
}
var sortedList =
source.OrderBy(number => number).ToList();
int itemIndex = sortedList.Count / 2;
if (sortedList.Count % 2 == 0)
{
// Even number of items.
return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
}
else
{
// Odd number of items.
return sortedList[itemIndex];
}
}
}
Każda metoda rozszerzenia jest wywoływana dla dowolnej kolekcji wyliczalnej w taki sam sposób, jak w przypadku wywoływania innych metod agregacji z interfejsu IEnumerable<T> .
Poniższy przykład kodu pokazuje, jak używać Median
metody dla tablicy typu double
.
double[] numbers = [1.9, 2, 8, 4, 5.7, 6, 7.2, 0];
var query = numbers.Median();
Console.WriteLine($"double: Median = {query}");
// This code produces the following output:
// double: Median = 4.85
Możesz przeciążyć metodę agregacji , aby akceptowała sekwencje różnych typów. Standardowe podejście polega na utworzeniu przeciążenia dla każdego typu. Innym podejściem jest utworzenie przeciążenia, które przyjmuje typ ogólny i konwertowanie go na określony typ przy użyciu delegata. Można również połączyć oba podejścia.
Można utworzyć określone przeciążenie dla każdego typu, który chcesz obsługiwać. Poniższy przykład kodu przedstawia przeciążenie Median
metody dla int
typu.
// int overload
public static double Median(this IEnumerable<int> source) =>
(from number in source select (double)number).Median();
Teraz można wywołać Median
przeciążenia dla typów integer
i double
, jak pokazano w poniższym kodzie:
double[] numbers1 = [1.9, 2, 8, 4, 5.7, 6, 7.2, 0];
var query1 = numbers1.Median();
Console.WriteLine($"double: Median = {query1}");
int[] numbers2 = [1, 2, 3, 4, 5];
var query2 = numbers2.Median();
Console.WriteLine($"int: Median = {query2}");
// This code produces the following output:
// double: Median = 4.85
// int: Median = 3
Można również utworzyć przeciążenie, które akceptuje ogólną sekwencję obiektów. To przeciążenie przyjmuje delegata jako parametr i używa go do konwertowania sekwencji obiektów typu ogólnego na określony typ.
Poniższy kod przedstawia przeciążenie Median
metody, która przyjmuje delegata Func<T,TResult> jako parametr. Ten delegat przyjmuje obiekt typu ogólnego T i zwraca obiekt typu double
.
// generic overload
public static double Median<T>(
this IEnumerable<T> numbers, Func<T, double> selector) =>
(from num in numbers select selector(num)).Median();
Teraz można wywołać metodę Median
dla sekwencji obiektów dowolnego typu. Jeśli typ nie ma własnego przeciążenia metody, musisz przekazać parametr delegata. W języku C#do tego celu można użyć wyrażenia lambda. Ponadto tylko w języku Visual Basic, jeśli używasz klauzuli Aggregate
lub Group By
zamiast wywołania metody, możesz przekazać dowolną wartość lub wyrażenie, które znajduje się w zakresie tej klauzuli.
Poniższy przykładowy kod pokazuje, jak wywołać metodę Median
dla tablicy liczb całkowitych i tablicy ciągów. W przypadku ciągów obliczana jest mediana długości ciągów w tablicy. W przykładzie pokazano, jak przekazać parametr delegata Func<T,TResult> do Median
metody dla każdego przypadku.
int[] numbers3 = [1, 2, 3, 4, 5];
/*
You can use the num => num lambda expression as a parameter for the Median method
so that the compiler will implicitly convert its value to double.
If there is no implicit conversion, the compiler will display an error message.
*/
var query3 = numbers3.Median(num => num);
Console.WriteLine($"int: Median = {query3}");
string[] numbers4 = ["one", "two", "three", "four", "five"];
// With the generic overload, you can also use numeric properties of objects.
var query4 = numbers4.Median(str => str.Length);
Console.WriteLine($"string: Median = {query4}");
// This code produces the following output:
// int: Median = 3
// string: Median = 4
Interfejs można rozszerzyć IEnumerable<T> za pomocą niestandardowej metody zapytania, która zwraca sekwencję wartości. W takim przypadku metoda musi zwrócić kolekcję typu IEnumerable<T>. Takie metody mogą służyć do stosowania filtrów lub przekształceń danych do sekwencji wartości.
W poniższym przykładzie pokazano, jak utworzyć metodę rozszerzenia o nazwie AlternateElements
, która zwraca każdy inny element w kolekcji, zaczynając od pierwszego elementu.
// Extension method for the IEnumerable<T> interface.
// The method returns every other element of a sequence.
public static IEnumerable<T> AlternateElements<T>(this IEnumerable<T> source)
{
int index = 0;
foreach (T element in source)
{
if (index % 2 == 0)
{
yield return element;
}
index++;
}
}
Tę metodę rozszerzenia można wywołać dla dowolnej kolekcji wyliczalnej, tak jak wywołasz inne metody z interfejsu IEnumerable<T> , jak pokazano w poniższym kodzie:
string[] strings = ["a", "b", "c", "d", "e"];
var query5 = strings.AlternateElements();
foreach (var element in query5)
{
Console.WriteLine(element);
}
// This code produces the following output:
// a
// c
// e
Każdy przykład pokazany w tym artykule ma inny odbiornik. Oznacza to, że każda metoda musi być zadeklarowana w innym bloku rozszerzenia, który określa unikatowy odbiornik. Poniższy przykład kodu przedstawia pojedynczą klasę statyczną z trzema różnymi blokami rozszerzeń, z których każda zawiera jedną z metod zdefiniowanych w tym artykule:
public static class EnumerableExtension
{
extension(IEnumerable<double>? source)
{
public double Median()
{
if (source is null || !source.Any())
{
throw new InvalidOperationException("Cannot compute median for a null or empty set.");
}
var sortedList =
source.OrderBy(number => number).ToList();
int itemIndex = sortedList.Count / 2;
if (sortedList.Count % 2 == 0)
{
// Even number of items.
return (sortedList[itemIndex] + sortedList[itemIndex - 1]) / 2;
}
else
{
// Odd number of items.
return sortedList[itemIndex];
}
}
}
extension(IEnumerable<int> source)
{
public double Median() =>
(from number in source select (double)number).Median();
}
extension<T>(IEnumerable<T> source)
{
public double Median(Func<T, double> selector) =>
(from num in source select selector(num)).Median();
public IEnumerable<T> AlternateElements()
{
int index = 0;
foreach (T element in source)
{
if (index % 2 == 0)
{
yield return element;
}
index++;
}
}
}
}
Końcowy blok rozszerzenia deklaruje ogólny blok rozszerzenia. Parametr typu dla odbiornika jest zadeklarowany na samym urządzeniu extension
.
W poprzednim przykładzie zadeklarowany jest jeden element członkowski rozszerzenia w każdym bloku rozszerzenia. W większości przypadków utworzysz wiele elementów członkowskich rozszerzenia dla tego samego odbiornika. W takich przypadkach należy zadeklarować rozszerzenia dla tych elementów członkowskich w jednym bloku rozszerzenia.