NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# 17-4. 클래스의 확장 메서드. (Extension method)

    페이지 정보

    본문

    안녕하세요. 소심비형입니다. 자주 사용되지는 않지만, 필요한 곳에 적절하게 사용하면 아주 유용하게 사용할 수 있는 확장 메서드에 대해서 알아보겠습니다. 확장 메서드를 사용하면 새 파생 형식을 만들거나 다시 컴파일하거나 원래 형식을 수정하지 않고도 기존 형식에 메서드를 "추가"할 수 있습니다. 확장 메서드는 특수한 종류의 정적 메서드로 확장하려는 형식의 인스턴스 메서드인 것처럼 동작합니다.

     

    다음의 예제를 통해서 알아보도록 합시다.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using MyExtension;
     
    namespace MyExtension
    {
        /// <summary>    
        /// 평균을 처리하는 여러가지 방법을 제공합니다.    
        /// </summary>    
        public static class AverageExtension
        {
            /// <summary>        
            /// 요소 집단의 평균값을 가져옵니다.        
            /// <para>모든 요소의 값을 더한 후 요소의 수로 나눈 값입니다.</para>        
            /// </summary>        
            /// <param name="values">Mean을 계산하기 위한 요소의 집단입니다.</param>        
            /// <returns>평균값을 반환합니다.</returns>        
            public static double Mean(this double[] values)
            {
                return Mean(values.AsEnumerable());
            }
     
            /// <summary>        
            /// 요소 집단의 평균값을 가져옵니다.        
            /// <para>모든 요소의 값을 더한 후 요소의 수로 나눈 값입니다.</para>        
            /// </summary>        
            /// <param name="values">Mean을 계산하기 위한 요소의 집단입니다.</param>        
            /// <returns>평균값을 반환합니다.</returns>        
            public static double Mean(this IEnumerable<double> values)
            {
                return values.Average();
            }
     
            /// <summary>        
            /// 요소 집단의 중앙값을 가져옵니다.        
            /// <para>요소의 수를 정렬한 후 중앙에 위치하는 값을 반환합니다.</para>        
            /// <para>ex) { 1, 1, 1, 1, 4, 100, 100, 100, 100 }의 배열에서 중앙값은 4입니다. 만약 100이 하나 더 있다면, 중앙값은 (4 + 100) / 2입니다.</para>        
            /// </summary>        
            /// <param name="values">Median을 계산하기 위한 요소의 집단입니다.</param>        
            /// <returns>중앙값을 반환합니다.</returns>        
            public static double Median(this double[] values)
            {
                return Median(values.AsEnumerable());
            }
     
            /// <summary>        
            /// 요소 집단의 중앙값을 가져옵니다.        
            /// <para>요소의 수를 정렬한 후 중앙에 위치하는 값을 반환합니다.</para>        
            /// <para>ex) { 1, 1, 1, 1, 4, 100, 100, 100, 100 }의 배열에서 중앙값은 4입니다. 만약 100이 하나 더 있다면, 중앙값은 (4 + 100) / 2입니다.</para>        
            /// </summary>        
            /// <param name="values">Median을 계산하기 위한 요소의 집단입니다.</param>        
            /// <returns>중앙값을 반환합니다.</returns>        
            public static double Median(this IEnumerable<double> values)
            {
                List<double> orderedList = values.OrderBy(numbers => numbers).ToList();
                int listSize = orderedList.Count;
                double result;
                if (listSize % 2 == 0) // even            
                {
                    int midIndex = listSize / 2;
                    result = ((orderedList.ElementAt(midIndex - 1) + orderedList.ElementAt(midIndex)) / 2);
                }
                else // odd            
                {
                    double element = (double)listSize / 2;
                    element = Math.Round(element, MidpointRounding.AwayFromZero);
                    result = orderedList.ElementAt((int)(element - 1));
                }
     
                return result;
            }
     
            /// <summary>        
            /// 요소 집단의 최빈값을 가져옵니다.        
            /// <para>요소의 분포에서 최다 도수를 갖는 값을 반환합니다.</para>        
            /// <para>ex) { 1, 2, 2, 3, 4, 5, 5, 6, 7 } 배열에서 최빈값은 2와 5가 됩니다. 만약, 2가 하나 더 포함된다면 최빈값은 2가 됩니다.</para>        
            /// </summary>        
            /// <param name="values">Modes를 계산하기 위한 요소의 집단입니다.</param>        
            /// <returns>최빈값을 반환합니다.</returns>        
            public static double[] Modes(this double[] values)
            {
                return Modes(values.AsEnumerable()).ToArray();
            }
     
            /// <summary>        
            /// 요소 집단의 최빈값을 가져옵니다.        
            /// <para>요소의 분포에서 최다 도수를 갖는 값을 반환합니다.</para>        
            /// <para>ex) { 1, 2, 2, 3, 4, 5, 5, 6, 7 } 배열에서 최빈값은 2와 5가 됩니다. 만약, 2가 하나 더 포함된다면 최빈값은 2가 됩니다.</para>        
            /// </summary>        
            /// <param name="values">Modes를 계산하기 위한 요소의 집단입니다.</param>        
            /// <returns>최빈값을 반환합니다.</returns>        
            public static IEnumerable<double> Modes(this IEnumerable<double> values)
            {
                var list = values.Where(v => v == 0).ToList();
                int cnt = list.Count;
                var modesList = values.GroupBy(group => group).Select(valueCluster => new { Value = valueCluster.Key, Occurrence = valueCluster.Count(), }).ToList();
                int maxOccurrence = modesList.Max(g => g.Occurrence);
                return modesList.Where(x => x.Occurrence == maxOccurrence && maxOccurrence > 1).Select(x => x.Value);
            }
        }
    }
     
    namespace ExtensionMethod
    {
        class MainApp
        {
            static void Main(string[] args)
            {
                double[] data = new double[] { 1.5, 1.2, 1.7, 1.2, 1.1, 1.9, 1.6, 1.7 };
                Console.WriteLine("평균값 : {0}", data.Mean());
                Console.WriteLine("중앙값 : {0}", data.Median());
                Console.WriteLine("최빈값 : {0}", string.Join(", ", data.Modes().Select(d => d.ToString()).ToArray()));
                Console.Read();
            }
        }
    }

     

     

    위의 예제는 NG Framework에서 진행중인 내용을 가져와서 적용시킨 소스입니다. 실행 시키면 아래와 같은 결과를 얻을 수 있습니다.

     

     

    라인 19에서 사용된 코드를 보세요. 지금까지 메서드나 생성자에서는 볼 수 없었던 시그니처를 이용하고 있습니다. 바로 this 키워드입니다. 코드가 좀 복잡해 보일수도 있습니다. 메서드 오버로딩은 무시하고 첫번째 메서드만 보면 됩니다.

    public static double Mean(this double[] values)

     

     

    확장메서드를 구현하려면 몇가지 지켜야할 사항이 있습니다.

    1. 정적(static) 클래스안에서 확장메서드를 구현해야 합니다.
            public static class AverageExtension
    
    1. 정적 클래스이므로 접근 제한자에 protected를 사용할 수 없습니다. 당연히 internal protected도 불가능합니다.
    2. 정적 클래스이므로 메서드도 정적(static)으로 구현해야 합니다.
            public static double Mean(this double[] values)
    
    1. 메서드의 첫번째 매개 변수는 메서드가 작동하는 형식을 지정하며 앞에 this한정자가 있어야 합니다.
    2. 확장메서드를 호출하는 코드에서 using지시문을 추가하여 확장메서드가 속한 클래스의 네임스페이스를 지정해야 합니다.
            using MyExtension;
    
    1. 형식의 인스턴스 메서드인 것처럼 확장메서드를 호출하여 사용합니다. 첫번째 매개 변수는 연산자가 적용되는 형식을 나타내며 컴파일러가 개체의 형식을 이미 알고 있으므로 호출 코드에서 지정할 필요는 없습니다. 매개 변수 2번째부터 n까지에 대한 인수만 제공하면 됩니다.

     

     

    일반적으로 반드시 필요한 경우에만 드물게 사용하고, 가능하면 기존 형식을 확장해야 하는 클라이언트 코드는 파생된 새로운 형식을 만드는게 좋습니다. 이 부분은 상속 및 추상화와 관계가 있으므로 다음 강좌에서 진행하도록 하겠습니다. 아무래도 상속과 추상화가 나오면 OOP에 대해서도 이야기해야 하는데 OOP만해도 워낙 광범위한데다가 간단하게 설명할만큼 잘 알지도 못합니다. 이야기가 길어지므로 따로 정리하는 시간을 갖도록 하는게 좋겠습니다.

    oezQksh.gif

     

     

    자주 사용되지는 않지만 확장메서드를 이용해서 좀 더 편리하게 추가 또는 확장 기능을 제공할 수 있습니다.

    다음 시간에...

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.