방법: LINQ 쿼리용 사용자 지정 메서드 추가

IEnumerable<T> 인터페이스에 확장 메서드를 추가하여 LINQ 쿼리에 사용할 수 있는 메서드 집합을 확장할 수 있습니다. 예를 들어, 일반적인 평균 또는 최대값 연산 외에도 값 시퀀스에서 단일 값을 계산하기 위해 사용자 지정 집계 메서드를 만들 수 있습니다. 또한 값 시퀀스에 사용자 지정 필터나 특정 데이터 변환을 적용하고 새 시퀀스를 반환하는 메서드를 만들 수도 있습니다. 이러한 메서드의 예로는 Distinct, Skip<TSource>Reverse<TSource>가 있습니다.

IEnumerable<T> 인터페이스를 확장하면 열거 가능한 모든 컬렉션에 사용자 지정 메서드를 적용할 수 있습니다. 자세한 내용은 확장 메서드(C# 프로그래밍 가이드) 또는 확장 메서드(Visual Basic)를 참조하십시오.

집계 메서드 추가

집계 메서드는 값 집합에서 단일 값을 계산합니다. LINQ에서는 Average, MinMax를 비롯한 여러 가지 집계 메서드를 제공합니다. IEnumerable<T> 인터페이스에 확장 메서드를 추가하여 사용자 고유의 집계 메서드를 만들 수 있습니다.

다음 코드 예제에서는 Median이라는 확장 메서드를 만들어 double 형식의 숫자 시퀀스에 대한 중앙값을 계산하는 방법을 보여 줍니다.

Imports System.Runtime.CompilerServices

Module LINQExtension

    ' Extension method for the IEnumerable(of T) interface. 
    ' The method accepts only values of the Double type.
    <Extension()> 
    Function Median(ByVal source As IEnumerable(Of Double)) As Double
        If source.Count = 0 Then
            Throw New InvalidOperationException("Cannot compute median for an empty set.")
        End If

        Dim sortedSource = From number In source 
                           Order By number

        Dim itemIndex = sortedSource.Count \ 2

        If sortedSource.Count Mod 2 = 0 Then
            ' Even number of items in list.
            Return (sortedSource(itemIndex) + sortedSource(itemIndex - 1)) / 2
        Else
            ' Odd number of items in list.
            Return sortedSource(itemIndex)
        End If
    End Function
End Module
public static class LINQExtension
{
    public static double Median(this IEnumerable<double> source)
    {
        if (source.Count() == 0)
        {
            throw new InvalidOperationException("Cannot compute median for an empty set.");
        }

        var sortedList = from number in source
                         orderby number
                         select number;

        int itemIndex = (int)sortedList.Count() / 2;

        if (sortedList.Count() % 2 == 0)
        {
            // Even number of items.
            return (sortedList.ElementAt(itemIndex) + sortedList.ElementAt(itemIndex - 1)) / 2;
        }
        else
        {
            // Odd number of items.
            return sortedList.ElementAt(itemIndex);
        }
    }
}

열거 가능한 컬렉션에 대해 이 확장 메서드를 호출하는 방법은 IEnumerable<T> 인터페이스에서 다른 집계 메서드를 호출하는 방법과 같습니다.

참고

Visual Basic에서는 Aggregate 또는 Group By 절에 메서드 호출이나 표준 쿼리 구문을 사용할 수 있습니다. 자세한 내용은 Aggregate 절(Visual Basic)Group By 절(Visual Basic)을 참조하십시오.

다음 코드 예제에서는 double 형식의 배열에 Median 메서드를 사용하는 방법을 보여 줍니다.

        Dim numbers1() As Double = {1.9, 2, 8, 4, 5.7, 6, 7.2, 0}

        Dim query1 = Aggregate num In numbers1 Into Median()

        Console.WriteLine("Double: Median = " & query1)



...


        ' This code produces the following output:
        '
        ' Double: Median = 4.85

        double[] numbers1 = { 1.9, 2, 8, 4, 5.7, 6, 7.2, 0 };

        var query1 = numbers1.Median();

        Console.WriteLine("double: Median = " + query1);



...


/*
 This code produces the following output:

 Double: Median = 4.85
*/

다양한 형식을 받아들이도록 집계 메서드 오버로드

다양한 형식의 시퀀스를 받아들이도록 집계 메서드를 오버로드할 수 있습니다. 이렇게 하는 일반적인 방법은 각 형식에 대해 오버로드를 만드는 것입니다. 또 다른 방법은 대리자를 사용하여 제네릭 형식을 받아들인 다음 특정 형식으로 변환하는 오버로드를 만드는 것입니다. 두 방법을 함께 사용할 수도 있습니다.

각 형식에 대해 오버로드를 만들려면

지원할 각 형식에 대해 특정 오버로드를 만들 수 있습니다. 다음 코드 예제에서는 integer 형식에 대한 Median 메서드의 오버로드를 보여 줍니다.

' Integer overload

<Extension()> 
Function Median(ByVal source As IEnumerable(Of Integer)) As Double
    Return Aggregate num In source Select CDbl(num) Into med = Median()
End Function
//int overload

public static double Median(this IEnumerable<int> source)
{
    return (from num in source select (double)num).Median();
}

이제 다음 코드와 같이 integer 및 double 형식 둘 다에 대해 Median 오버로드를 호출할 수 있습니다.

        Dim numbers1() As Double = {1.9, 2, 8, 4, 5.7, 6, 7.2, 0}

        Dim query1 = Aggregate num In numbers1 Into Median()

        Console.WriteLine("Double: Median = " & query1)



...


        Dim numbers2() As Integer = {1, 2, 3, 4, 5}

        Dim query2 = Aggregate num In numbers2 Into Median()

        Console.WriteLine("Integer: Median = " & query2)



...


' This code produces the following output:
'
' Double: Median = 4.85
' Integer: Median = 3
        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
 Integer: Median = 3
*/

제네릭 오버로드를 만들려면

제네릭 개체의 시퀀스를 받아들이는 오버로드를 만들 수도 있습니다. 이 오버로드는 대리자를 매개 변수로 사용하여 제네릭 형식 개체의 시퀀스를 특정 형식으로 변환할 수 있습니다.

다음 코드에서는 Func<T, TResult> 대리자를 매개 변수로 사용하는 Median 메서드의 오버로드를 보여 줍니다. 이 대리자는 제네릭 형식 T의 개체를 받아들인 다음 double 형식의 개체를 반환합니다.

' Generic overload.

<Extension()> 
Function Median(Of T)(ByVal source As IEnumerable(Of T), 
                      ByVal selector As Func(Of T, Double)) As Double
    Return Aggregate num In source Select selector(num) Into med = Median()
End Function
// Generic overload.

public static double Median<T>(this IEnumerable<T> numbers,
                       Func<T, double> selector)
{
    return (from num in numbers select selector(num)).Median();
}

이제 원하는 형식 개체의 시퀀스에 대해 Median 메서드를 호출할 수 있습니다. 해당 형식에 고유한 메서드 오버로드가 없을 경우 대리자 매개 변수를 전달해야 합니다. Visual Basic과 C#에서는 이를 위해 람다 식을 사용할 수 있습니다. 또한 Visual Basic에서는 메서드 호출 대신 Aggregate 또는 Group By 절을 사용하는 경우 이 절의 범위에 있는 값이나 식을 전달할 수도 있습니다.

다음 코드 예제에서는 정수 배열과 문자열 배열에 대해 Median 메서드를 호출하는 방법을 보여 줍니다. 문자열의 경우 배열에 있는 문자열의 길이에 대한 중앙값이 계산됩니다. 이 예제에서는 각각의 경우에서 Median 메서드에 Func<T, TResult> 대리자 매개 변수를 전달하는 방법을 보여 줍니다.

Dim numbers3() As Integer = {1, 2, 3, 4, 5}

' You can use num 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.

Dim query3 = Aggregate num In numbers3 Into Median(num)

Console.WriteLine("Integer: Median = " & query3)

Dim numbers4() As String = {"one", "two", "three", "four", "five"}

' With the generic overload, you can also use numeric properties of objects.

Dim query4 = Aggregate str In numbers4 Into Median(str.Length)

Console.WriteLine("String: Median = " & query4)

' This code produces the following output:
'
' Integer: Median = 3
' String: Median = 4
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:

 Integer: Median = 3
 String: Median = 4
*/

컬렉션을 반환하는 메서드 추가

값 시퀀스를 반환하는 사용자 지정 쿼리 메서드를 사용하여 IEnumerable<T> 인터페이스를 확장할 수 있습니다. 이 경우 메서드는 IEnumerable<T> 형식의 컬렉션을 반환해야 합니다. 이러한 메서드는 값 시퀀스에 필터나 데이터 변환을 적용하는 데 사용될 수 있습니다.

다음 예제에서는 첫 번째 요소부터 시작하여 컬렉션에 있는 요소를 하나씩 걸러서 반환하는 AlternateElements라는 확장 메서드를 만드는 방법을 보여 줍니다.

' Extension method for the IEnumerable(of T) interface. 
' The method returns every other element of a sequence.

<Extension()> 
Function AlternateElements(Of T)(
    ByVal source As IEnumerable(Of T)
    ) As IEnumerable(Of T)

    Dim list As New List(Of T)
    Dim i = 0
    For Each element In source
        If (i Mod 2 = 0) Then
            list.Add(element)
        End If
        i = i + 1
    Next
    Return list
End Function
// 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)
{
    List<T> list = new List<T>();

    int i = 0;

    foreach (var element in source)
    {
        if (i % 2 == 0)
        {
            list.Add(element);
        }

        i++;
    }

    return list;
}

다음 코드와 같이 IEnumerable<T> 인터페이스에서 다른 메서드를 호출할 때와 같은 방법으로 열거 가능한 모든 컬렉션에 대해 이 확장 메서드를 호출할 수 있습니다.

Dim strings() As String = {"a", "b", "c", "d", "e"}

Dim query = strings.AlternateElements()

For Each element In query
    Console.WriteLine(element)
Next

' This code produces the following output:
'
' a
' c
' e
string[] strings = { "a", "b", "c", "d", "e" };

var query = strings.AlternateElements();

foreach (var element in query)
{
    Console.WriteLine(element);
}
/*
 This code produces the following output:

 a
 c
 e
*/

참고 항목

참조

IEnumerable<T>

확장 메서드(C# 프로그래밍 가이드)

개념

확장 메서드(Visual Basic)