.NET 2.0 부터 제공되는 Generic 형 Class. System.Collection.Generic 이라는 네임스페이스에 존재하는 내용이다. Collection에서는 모든 값들을 Object를 사용하기 때문에 매번 Casting 을 해야 했고, 그에 따른 성능상 문제로 인해 대안점 처럼 꺼내든 것이다. 뭐 Generic의 역사나 기타 방향성문제는 다른 문서들을 참고하시고..

여기서는 generic에 있는 각 형태에서 제공하는 find라는 함수를 이용하는 방법을 언급하려 한다. 물론 foreach와 같은 순환문을 이용해서 직접 find 함수를 구현할 수 있지만, 최소한 내부에 구현된 find 함수의 성능이 그나마 내가 직접 짠 것 보다는 좋을 것이라는 생각에 최대한 애용하는 편이다.

이 find 함수의 기본 형은 아래와 같다.

public T Find (
	Predicate<t> match
)

 

위의 형태에 대한 정의 내용을 보면 Predicate<T> 라는 게 있는데, 바로 이 부분에 대리자 역할을 하는 함수가 필요로 한다. match 라는 의미는 자신이 찾는 값의 형태인 경우에는 true 혹은 false를 돌려달라는 의미다.

여기서 개인적으로 가진 의문은 왜 find를 저렇게 구현했을까 했던 점이다. 만일 List<int> aaa 라는 개체를 만들었다면, aaa.find(1111) 식으로 aaa.find(<T>) 형태로 제공되었으면 그냥 썼을텐데, 저런식으로 만드니 원 접근이 될 터인가.. 했다. 하지만, 정작 <T> 안의 값의 형태가 복합형이고, 그 안에 찾는 조건이 복잡한 경우라면 오히려 위의 방법은 좋은 대안 식이라고나 할까?

 

일단 MSDN에서 제공되는 예제를 먼저 보자.

using System;
using System.Collections.Generic;

public class Example
{
    public static void Main()
    {
        List<string> dinosaurs = new List<string>();

        dinosaurs.Add("Compsognathus");
        dinosaurs.Add("Amargasaurus");
        dinosaurs.Add("Oviraptor");
        dinosaurs.Add("Velociraptor");
        dinosaurs.Add("Deinonychus");
        dinosaurs.Add("Dilophosaurus");
        dinosaurs.Add("Gallimimus");
        dinosaurs.Add("Triceratops");

        Console.WriteLine("\nFind(EndsWithSaurus): {0}", 
            dinosaurs.Find(EndsWithSaurus));
    }

    // Search predicate returns true if a string ends in "saurus".
    private static bool EndsWithSaurus(String s)
    {
        if ((s.Length > 5) && 
            (s.Substring(s.Length - 6).ToLower() == "saurus"))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

 

끝의 글자가 saurus 로 끝나는 명칭을 찾는 로직. 저기서 보면 Find 안에 특정 값이 들어간게 아니고, 특정 함수가 들어가 있다. 즉 값의 결과가 true/false로 떨어지는 독자적인 함수가 있으면 된다.  위의 예제는 바로 List에 담긴 내용들 중 맨 끝의 글자가 saurus가 들어 있는지 체크한 뒤 있으면 true, 없으면 false로 돌려준다.

대충 이해가 되려나 싶다.

그런데, 내가 걸린 문제는 이렇다. 위의 예제대로라면, 실제 find를 사용하는 것은 그 찾으려는 값이 밖에 있다는 사실. 만일 위의 예제대로 라면, 찾기 로직 안에 saurus 라는 값 처럼 아예 박혀 있어야 되는데, 실제로는 저 값이 외부에서 받는 값일때는 걸리게 된다.

예를 들어보자. 사용자가 검색하는 단어를 기준으로 찾는다고 하자.
입력되는 값에 따라 찾는 내용이 달라져야 되는데, 이 경우에는 위와 같은 방법을 사용하면 곤란해질 수 밖에 없다. 그렇다면?

위의 예제를 보면 함수를 넣기 위해 static private로 만들었는데, 그것을 delegate라는 대리자를 사용해서 원큐로 만드는 것이다. txtInput 이라는 Text 박스에서 입력값이 생겼을 때 찾기 명령을 실행 시키는 예제로직을 만들어보았다.

List<string> aryData = new List<string>();

public Test()
{
    aryData.Add("apple");
    aryData.Add("pineapple");
    aryData.Add("steel");
}

private void txtInput_TextChanged(object sender, EventArgs e)
{
    string sResult = aryData.Find(delegate(string s) {
        return s.Equals(txtInput.Text);
    });

    if(string.IsNullOrEmpty(sResult))
        MessageBox.Show(sResult);
}

 

대략적으로 보면 알겠지만,

Find 라는 함수 뒤에 delegate 라는 대리자를 선언하고, 그 안에 가상 함수로 들어오는 파라미터 값을 정의한다. string s 부분이 있는데, 그 부분이 바로 Find 에서 역으로 호출 될 때 들어오는 값이다. 만일 List<int> 라면 int 가 되어야 할 것이다.

그리고 최종적으로 돌려주는 값은 true, false로 나올 수 있는 값이면 된다.

그러면 실제적으로 Find의 Return 값은 찾은 값을 돌려주게 된다. 지금은 List<string> 이니 돌려주는 값은 당연히 string. 만일 int면 int가 될 것이다.

 

정리하며.

물론 사람이 만든 코드이기 때문에, 성능이 월등하리라 볼 수는 없다. 하지만, 최소한 그 분야의 대가들이며, 또 내부적인 구현 시, 순수 .NET으로 만들지 않고, 성능을 위해 억지로 Binary로 만들기도 한다. 즉 알지는 못하나, 최소한 날로 짜는 것 보다 더 나은 성능을 보여줄 수 있다는 것이다.

이런 내 함수들을 활용 방법이 의외 복잡하더라도 작정하고 사용하다 보면 지금 보다 더 나은 성능을 보장할 수 있다.

 

복합형 값용 List<T>.Find 사용방법 예제.

사람 정보 중 이름이 같은 데이터를 메시지 박스에 표현 하는 방법

class PersonData
{
    public PersonData(string sName, string sSocialNo)
    {
        _name = sName;
        _socialNo = sSocialNo;
    }

    string _name;
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
        }
    }

    string _socialNo;
    public string SocialNo
    {
        get
        {
            return _socialNo;
        }
        set
        {
            _socialNo = value;
        }
    }

}

List<persondata> aryData = new List<persondata>();

public Test()
{
    aryData.Add(new PersonData("홍길동", "123444-444422"));
    aryData.Add(new PersonData("조조", "123444-444422"));
    aryData.Add(new PersonData("유비", "123444-444422"));
}

private void txtInput_TextChanged(object sender, EventArgs e)
{
    PersonData sResult = aryData.Find(delegate(PersonData p) {
        return p.Name.Equals(txtInput.Text);
    });

    if(sResult != null)
        MessageBox.Show(sResult.Name + sResult.SocialNo);
}
728x90

+ Recent posts