성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
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'>C# - ReadOnlySequence<T> 소개</h1> <p> 이번엔 다음의 글을 정리한 것에 불과합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CREATING A READONLYSEQUENCE FROM ARRAY DATA ; <a target='tab' href='https://www.stevejgordon.co.uk/creating-a-readonlysequence-from-array-data-in-dotnet'>https://www.stevejgordon.co.uk/creating-a-readonlysequence-from-array-data-in-dotnet</a> </pre> <br /> 그러니까, 개별 버퍼를 연결해 한 개의 개체로 다룰 수 있는 표현 방법을 .NET BCL 자체에서 제공한 것이 바로 ReadOnlySequence<T>입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ReadOnlySequence<T> Struct ; <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.buffers.readonlysequence-1'>https://docs.microsoft.com/en-us/dotnet/api/system.buffers.readonlysequence-1</a> </pre> <br /> 제가 "표현 방법"이라고 언급했는데, 왜냐하면 .NET BCL에서 이에 대한 모든 concrete 코드를 제공하지 않기 때문입니다. 대충 상황을 좀 보면, ReadOnlySequence<T>는 (기본 생성자 외에) 단일 버퍼를 받아들일 수 있는 3개의 생성자와,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public ReadOnlySequence(ReadOnlyMemory<T> memory) public ReadOnlySequence(T[] array) public ReadOnlySequence(T[] array, int start, int length) </pre> <br /> 다중 버퍼가 표현된 ReadOnlySequenceSegment<T>를 받아들일 수 있는 1개의 생성자를 제공합니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public ReadOnlySequence(ReadOnlySequenceSegment<T> startSegment, int startIndex, ReadOnlySequenceSegment<T> endSegment, int endIndex) </pre> <br /> 여기서 ReadOnlySequenceSegment<T> 타입은,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ReadOnlySequenceSegment<T> Class ; <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.buffers.readonlysequencesegment-1'>https://docs.microsoft.com/en-us/dotnet/api/system.buffers.readonlysequencesegment-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;' > namespace System.Buffers { public abstract class ReadOnlySequenceSegment<T> { public ReadOnlyMemory<T> Memory { get; protected set; } public ReadOnlySequenceSegment<T>? Next { get; protected set; } public long RunningIndex { get; protected set; } } } </pre> <br /> 따지고 보면 개념은 정말 간단합니다. 그냥 우리가 기존에 알고 있던 연결 리스트(Linked-List)와 Span의 구획 지정을 담아 명시적으로 ReadOnlySequenceSegment<T> 타입을 만든 것입니다.<br /> <br /> 자, 그럼 이걸로 별개의 배열을 연결하는 Segment 타입을 구현해 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>class ArraySegment<T> : ReadOnlySequenceSegment<T></span> { public ArraySegment(T [] array) { this.Memory = array; } public ArraySegment<T> Add(T [] array) { var segment = new ArraySegment<T>(array); segment.RunningIndex = RunningIndex + Memory.Length; Next = segment; return segment; } } </pre> <br /> 간단하죠. 다음은 이러한 Segment로 ReadOnlySequence를 활용할 차례입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class Program { static void Main(string[] args) { int[] array1 = { 1, 2, 3 }; int[] array2 = { 6, 7, 8, 9, 10 }; <span style='color: blue; font-weight: bold'>ArraySegment<int> segment1 = new ArraySegment<int>(array1); ArraySegment<int> segment2 = segment1.Add(array2);</span> ReadOnlySequence<int> seq = <span style='color: blue; font-weight: bold'>new ReadOnlySequence<int>(segment1, 0, segment2, segment2.Memory.Length);</span> foreach (var segment in seq) { foreach (int item in segment.Span) { Console.Write(item + ", "); } } Console.WriteLine(); } } /* 출력 결과 1, 2, 3, 6, 7, 8, 9, 10 */ </pre> <br /> ReadOnlySequence의 생성자에 segment 2개를 전달했는데, 이것은 연결 리스트의 첫 번째와 끝에 해당하는 segment입니다. 따라서, 가령 3개의 버퍼를 구성한다면 이렇게 처리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > { int[] array1 = { 1, 2, 3 }; int[] array2 = { 4, 5, }; int[] array3 = { 6, 7, 8, 9, 10 }; ArraySegment<int> <span style='color: blue; font-weight: bold'>segment1</span> = new ArraySegment<int>(array1); ArraySegment<int> <span style='color: blue; font-weight: bold'>segment3 = segment1.Add(array2).Add(array3);</span> ReadOnlySequence<int> seq = new ReadOnlySequence<int>(<span style='color: blue; font-weight: bold'>segment1, 0, segment3, segment3.Memory.Length</span>); foreach (var segment in seq) { foreach (int item in segment.Span) { Console.Write(item + ", "); } } Console.WriteLine(); } /* 출력 결과 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, */ </pre> <br /> 코드를 작성해 봤으니, 이제 다음의 그림 한장으로 ReadOnlySequence<T>의 역할을 이해할 수 있을 것입니다.<br /> <br /> [출처: <a target='tab' href='https://www.codemag.com/article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T'>https://www.codemag.com/article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T</a>]<br /> <img alt='seq_buffer_1.png' src='/SysWebRes/bbs/seq_buffer_1.png' /><br /> <br /> <hr style='width: 50%' /><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;' > public static class Extension { public static IEnumerable<T> AsEnumerable<T>(this ReadOnlySequence<T> seq) { foreach (var segment in seq) { int len = GetLength(segment); // <a target='tab' href='https://www.sysnet.pe.kr/2/0/12481'>https://www.sysnet.pe.kr/2/0/12481</a> for (int i = 0;i < len; i ++) { yield return GetItem(segment, i); } } } private static T GetItem<T>(ReadOnlyMemory<T> segment, int index) { return segment.Span[index]; // 성능 손실! <a target='tab' href='https://www.sysnet.pe.kr/2/0/12475'>https://www.sysnet.pe.kr/2/0/12475</a> } private static int GetLength<T>(ReadOnlyMemory<T> segment) { return segment.Length; } } </pre> <br /> 마이크로소프트 측은 대신 별도의 reader를 정의하는 식으로 해결을 했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SequenceReader<T> Struct ; <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.buffers.sequencereader-1'>https://docs.microsoft.com/en-us/dotnet/api/system.buffers.sequencereader-1</a> AN INTRODUCTION TO SEQUENCEREADER ; <a target='tab' href='https://www.stevejgordon.co.uk/an-introduction-to-sequencereader'>https://www.stevejgordon.co.uk/an-introduction-to-sequencereader</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;' > SequenceReader<int> reader = new SequenceReader<int>(seq); while (reader.End is false) { if (reader.TryRead(out int item) == true) { Console.Write($"{item}, "); } } </pre> <br /> 참고로, SequenceReader 역시 <a target='tab' href='https://www.sysnet.pe.kr/2/0/11530'>ref struct</a>이기 때문에 enumerable 메서드는 작성할 수 없습니다. (게다가 이번엔 우회 방법도 없습니다.)<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 IEnumerable<T> AsEnumerable<T>(this ReadOnlySequence<T> seq) where T : unmanaged, IEquatable<T> { SequenceReader<T> reader = new SequenceReader<T>(seq); // Error CS4013 Instance of type 'SequenceReader<T>' cannot be used inside a nested function, query expression, iterator block or async method while (reader.End is false) { if (reader.TryRead(out T item) == true) { yield return item; } } } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 정리해 보면!<br /> <br /> 예전에는, 간혹 버퍼 할당을 불연속적으로 하는 경우 나중에 결국 이를 하나로 합쳐서 반환/전달해야 하므로 GC Heap에 2중으로 부담을 갖게 됩니다. 바로 그런 경우에 그냥 그것을 하나로 표현할 수 있는 공통 방법으로 다룰 수 있도록 하는 것이 ReadOnlySequence<T>의 용도입니다. 그리고 사실 이건 기존에도 필요에 따라 얼마든지 구현해봤을 기능이고, 다시 말해 처음 나와서 낯설은 것일뿐 어찌 보면 별 거 아닙니다.<br /> <br /> 현재 BCL 내에서 이를 활용한 사례는 <a target='tab' href='https://www.nuget.org/packages/System.IO.Pipelines/'>System.IO.Pipelines</a>이고, 그것 내부에서는 ReadOnlySequenceSegment<T> 타입을 구현한 (internal로 정의된) BufferSegment가 있습니다.<br /> <br /> 마지막으로, <a target='tab' href='https://www.sysnet.pe.kr/2/0/12480#netfx'>.NET Framework의 경우 관련 타입을 System.Memory에 구현하고 있기 때문에 .NET 4.5부터 Nuget 참조를 통해 사용</a>할 수 있습니다.<br /> <br /> (첨부 파일은 이 글의 예제 코드를 포함합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2522
(왼쪽의 숫자를 입력해야 합니다.)