성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>LINQ에서의 Max 기능 구현</h1> <p> <br /> 이 글은, 다음의 질문 때문에 씌여진 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > LINQ 가장 큰 값 하나만 가져오기. ; <a target='tab' href='http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNO=8&no=140468&ref=140468&page=1'>http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNO=8&no=140468&ref=140468&page=1</a> </pre> <br /> 자... 문제를 여기다 다시 써볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Program tt = new Program(); tt.data = new List<FileInfo>(); tt.data.Add(new FileInfo { id = 1, filename = "AUpIs80.log", sended = true }); tt.data.Add(new FileInfo { id = 2, filename = "adminxplore.exe", sended = false }); tt.data.Add(new FileInfo { id = 3, filename = "LMDebug.log", sended = false }); tt.data.Add(new FileInfo { id = 4, filename = "LMDebug1.log", sended = false }); tt.data.Add(new FileInfo { id = 5, filename = "LMDebug1.log", sended = false }); var linq = from a in tt.data orderby a.id where a.sended == false select a.id; 안녕하세요 저런 코드에서 id가 가장 크거나 작은 것 하나만 셀렉트 하는 방법이 없나요? Max를 이용 하면 될거 같은데 쉽지 않네요... 그리고 저렇게 var linq에 담게 되면 실제 메모리에 새로 만들어 지는건가요? 아니면 LIST data의 주소 값만 참조 하는건가요? </pre> <br /> 우선, 원하는 값이 무엇이냐에 따라 달라집니다. 간단하게는 가장 큰 "id" 값을 원할 수 있을 텐데요. 그런 경우에는 굳이 LINQ까지 갈 필요없이 Max 확장 함수를 이용해서 다음과 같이 구할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var max = tt.data.Where(a => a.sended == false)<span style='color: blue; font-weight: bold'>.Max(a => a.id);</span> 결과: max == 5 </pre> <br /> 그런데, max == 5인 FileInfo 개체를 원한다면 어떻게 해야 할까요?<br /> <br /> 우선, 마이크로소프트의 예제 코드에 소개된 방식을 이용하여,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > How to: Find the Maximum Value in a Numeric Sequence (LINQ to SQL) ; <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/find-the-maximum-value-in-a-numeric-sequence'>https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/sql/linq/find-the-maximum-value-in-a-numeric-sequence</a> </pre> <br /> 다음과 같이 구현되어질 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var linq = from a in tt.data where a.sended == false && <span style='color: blue; font-weight: bold'>a.id == tt.data.Max(b => b.id)</span> select a; Console.WriteLine(linq.First().id); </pre> <br /> 결과야 나오긴 했지만, LINQ 구문을 잠시 살펴보면 매우 비효율적이라는 것을 알 수 있습니다. 왜냐하면, tt.data.Max(...)는 data에 있는 모든 요소들을 열람하게 되는데 그것의 실행 횟수가 역시 data 요소 수만큼 발생하기 때문입니다.<br /> <br /> 따라서, 이러한 쓸데없는 오버헤드를 줄이기 위해서는 다음과 같이 바꿔주는 것이 좋습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var max = tt.data.Where(a => a.sended == false).Max(a => a.id); var linq2 = from a in tt.data where a.sended == false && <span style='color: blue; font-weight: bold'>a.id == max</span> select a; </pre> <br /> 그런데... 왠지 너무 길다는 생각이 안드세요? 차라리 첫 번째 "var max = ...Max(...)" 구문에서 객체값을 반환해 준다면 좋겠다는 생각이 듭니다. 아쉽게도 기본 제공되는 Max 확장 함수에서는 그에 전달된 람다식의 값을 반환하게 되어 있기 때문에 약간의 변형을 해주어야 합니다.<br /> <br /> 이를 위해서 Max에 대한 별도의 확장 함수를 제공해 주면 되는데, 이에 대해서는 다음의 글에서 자세하게 설명해 주고 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Custom C# 3.0 LINQ Max Extension Method ; <a target='tab' href='http://geekswithblogs.net/michelotti/archive/2009/02/06/custom-c-3.0-linq-max-extension-method.aspx'>http://geekswithblogs.net/michelotti/archive/2009/02/06/custom-c-3.0-linq-max-extension-method.aspx</a> </pre> <br /> 위의 글에 따라 Max 함수를 만들어 주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public static class ExtensionMethod { public static FileInfo Max<TCompare>(this IEnumerable<FileInfo> collection, Func<FileInfo, TCompare> func) where TCompare : IComparable<TCompare> { FileInfo maxItem = null; TCompare maxValue = default(TCompare); foreach (var item in collection) { TCompare temp = func(item); if (maxItem == null || temp.CompareTo(maxValue) > 0) { maxValue = temp; maxItem = item; } } return maxItem; } } </pre> <br /> 이제 다음과 같이 자연스럽게 Max 값을 구해낼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var max = tt.data.Where(a => a.sended == false)<span style='color: blue; font-weight: bold'>.Max(a => a.id);</span> Console.WriteLine(<span style='color: blue; font-weight: bold'>max.id</span>); </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그런데, 한번 더 생각해 볼까요?<br /> <br /> 확장 함수 없이, 애당초 다음과 같은 구문으로 max를 구했으면 더 편했을 거 아닌가요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var max = tt.data.Where(a => a.sended == false).Max(<span style='color: blue; font-weight: bold'>a => a.id</span>); ==> var max = tt.data.Where(a => a.sended == false).Max(<span style='color: blue; font-weight: bold'>a => a</span>); </pre> <br /> 실제로 위의 구문을 실행하면 다음과 같은 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Unhandled Exception: System.ArgumentException: <span style='color: blue; font-weight: bold'>At least one object must implement IComparable.</span> at System.Collections.Comparer.Compare(Object a, Object b) at System.Collections.Generic.ObjectComparer`1.Compare(T x, T y) at System.Linq.Enumerable.Max[TSource](IEnumerable`1 source) at System.Linq.Enumerable.Max[TSource,TResult](IEnumerable`1 source, Func`2 selector) at ConsoleApplication1.Program.Main(String[] args) in D:\...[생략]...\Program.cs:line 36 </pre> <br /> 아하... 그저 FileInfo 개체에 비교가 가능하도록 만들어주면 되겠군요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class FileInfo : <span style='color: blue; font-weight: bold'>IComparable</span> { public int id { get; set; } public string filename { get; set; } public bool sended { get; set; } <span style='color: blue; font-weight: bold'>public int CompareTo(object other)</span> { FileInfo second = other as FileInfo; if (second == null) { return 1; } if (second.id > id) return -1; if (second.id == id) return 0; return 1; } } <span style='color: blue; font-weight: bold'>var max = tt.data.Where(a => a.sended == false).Max(a => a);</span> // 정상적으로 실행됨. Console.WriteLine(<span style='color: blue; font-weight: bold'>max.id</span>); </pre> <br /> <hr style='width: 50%' /><br /> <br /> 이 정도면, 웬만큼 Max 관련해서는 의문점이 풀렸겠지요. ^^ 그럼, 부가적으로 마지막 질문으로 넘어가 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 그리고 저렇게 var linq에 담게 되면 실제 메모리에 새로 만들어 지는건가요? 아니면 LIST data의 주소 값만 참조 하는건가요? </pre> <br /> 이에 대해서는 간단하게 테스트 해서 알아보는 방법이 있습니다.<br /> <br /> 새롭게 메모리가 할당되는 것이라면, 기존 개체의 값을 바꿨다고 해서 LINQ 계산값이 바뀌지는 않았을 것이라는 간단한 원칙을 이용하면 되는 것이지요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var max4 = tt.data.Where(a => a.sended == false).Max(a => a); tt.data[4].filename = "test"; Console.WriteLine(max4.filename); 출력: test </pre> <br /> 결과를 보니, tt.data 목록 안에 있는 개체의 참조값임을 알 수 있습니다. 메모리 할당이 아닌 참조값만 넘어왔다는 것입니다.<br /> <br /> <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=633&boardid=331301885'>첨부된 파일은 위의 코드를 포함한 예제 프로젝트</a>입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1271
(왼쪽의 숫자를 입력해야 합니다.)