거의 4 년의 경험 끝에, yield 키워드가 사용되는 코드를 보지 못했습니다 . 누군가이 키워드의 실제 사용법 (설명과 함께)을 보여 줄 수 있습니까? 그렇다면 가능한 방법을 쉽게 채우는 다른 방법이 있습니까?
yield
일반적으로 사용할 수있는 것에 대해 묻는 것이기 때문에 스택 오버플로에 적합하지 않습니다 .
거의 4 년의 경험 끝에, yield 키워드가 사용되는 코드를 보지 못했습니다 . 누군가이 키워드의 실제 사용법 (설명과 함께)을 보여 줄 수 있습니까? 그렇다면 가능한 방법을 쉽게 채우는 다른 방법이 있습니까?
yield
일반적으로 사용할 수있는 것에 대해 묻는 것이기 때문에 스택 오버플로에 적합하지 않습니다 .
답변:
이 yield
키워드는 훨씬 더 효율적인 컬렉션 항목에 대해 지연 열거를 효과적으로 만듭니다. 예를 들어 foreach
루프가 백만 개의 항목 중 처음 5 개 항목을 반복하는 경우 모두 yield
반환되며 내부적으로 먼저 백만 개의 항목을 수집하지 않았습니다. 마찬가지로 동일한 효율성을 달성하기 위해 자체 프로그래밍 시나리오에서 리턴 값 yield
과 함께 사용하려고합니다 IEnumerable<T>
.
특정 시나리오에서 얻는 효율성의 예
반복자 방법이 아니고 잠재적으로 큰 컬렉션을 비효율적으로 사용할 수 있습니다
(중간 컬렉션은 많은 항목으로 구성됨)
// Method returns all million items before anything can loop over them.
List<object> GetAllItems() {
List<object> millionCustomers;
database.LoadMillionCustomerRecords(millionCustomers);
return millionCustomers;
}
// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in GetAllItems()) {
num++;
if (num == 5)
break;
}
// Note: One million items returned, but only 5 used.
반복자 버전, 효율적
(중간 컬렉션이 작성되지 않음)
// Yields items one at a time as the caller's foreach loop requests them
IEnumerable<object> IterateOverItems() {
for (int i; i < database.Customers.Count(); ++i)
yield return database.Customers[i];
}
// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in IterateOverItems()) {
num++;
if (num == 5)
break;
}
// Note: Only 5 items were yielded and used out of the million.
다른 경우에는 목록을 정렬하고 병합하는 yield
것이 중간 모음으로 정렬되어 스왑되지 않고 원하는 순서로 항목을 다시 정렬하기 때문에 목록을 정렬하고 병합하는 것이 더 쉽습니다 . 많은 시나리오가 있습니다.
한 가지 예는 두 목록의 병합입니다.
IEnumerable<object> EfficientMerge(List<object> list1, List<object> list2) {
foreach(var o in list1)
yield return o;
foreach(var o in list2)
yield return o;
}
이 방법을 사용하면 하나의 연속 된 항목 목록이 생성되므로 중간 컬렉션이 필요없는 병합이 효과적으로 이루어집니다.
yield
키워드 만 (리턴 형 갖는 반복자 방법의 맥락에서 사용될 수있는 IEnumerable
, IEnumerator
, IEnumerable<T>
, 또는 IEnumerator<T>
.)과 함께 특별한 관계가있다 foreach
. 반복자는 특별한 방법입니다. MSDN 항복 문서 와 반복자 문서는 흥미로운 정보와 개념에 대한 설명을 많이 포함되어 있습니다. 과의 상관 관계를해야합니다 키워드 반복자의 이해를 보완하기 위해, 너무 그것에 대해 읽어.foreach
반복자가 효율성을 달성하는 방법을 배우려면 비밀은 C # 컴파일러가 생성 한 IL 코드에 있습니다. 반복자 방법으로 생성 된 IL은 일반 (비 반복자) 방법으로 생성 된 IL과 크게 다릅니다. 이 기사 (수익 키워드는 실제로 무엇을 생성합니까?)에서 이러한 종류의 통찰력을 제공합니다.
database.Customers.Count()
전체 고객 열거를 열거 하지 않으므로 모든 항목을 통과하는 데 더 효율적인 코드가 필요합니까?
얼마 전에 나는 실제적인 예를 보았습니다. 다음과 같은 상황이 있다고 가정 해 봅시다.
List<Button> buttons = new List<Button>();
void AddButtons()
{
for ( int i = 0; i <= 10; i++ ) {
var button = new Button();
buttons.Add(button);
button.Click += (sender, e) =>
MessageBox.Show(String.Format("You clicked button number {0}", ???));
}
}
버튼 객체는 컬렉션에서 자신의 위치를 알지 못합니다. 동일한 제한을 적용 Dictionary<T>
또는 다른 유형 모음.
yield
키워드를 사용하는 솔루션은 다음과 같습니다 .
interface IHasId { int Id { get; set; } }
class IndexerList<T>: List<T>, IEnumerable<T> where T: IHasId
{
List<T> elements = new List<T>();
new public void Clear() { elements.Clear(); }
new public void Add(T element) { elements.Add(element); }
new public int Count { get { return elements.Count; } }
new public IEnumerator<T> GetEnumerator()
{
foreach ( T c in elements )
yield return c;
}
new public T this[int index]
{
get
{
foreach ( T c in elements ) {
if ( (int)c.Id == index )
return c;
}
return default(T);
}
}
}
그리고 그것이 내가 그것을 사용하는 방법입니다.
class ButtonWithId: Button, IHasId
{
public int Id { get; private set; }
public ButtonWithId(int id) { this.Id = id; }
}
IndexerList<ButtonWithId> buttons = new IndexerList<ButtonWithId>();
void AddButtons()
{
for ( int i = 10; i <= 20; i++ ) {
var button = new ButtonWithId(i);
buttons.Add(button);
button.Click += (sender, e) =>
MessageBox.Show(String.Format("You clicked button number {0}", ( (ButtonWithId)sender ).Id));
}
}
for
색인을 찾기 위해 내 컬렉션을 반복 할 필요가 없습니다 . 내 버튼에는 ID가 있으며이 색인은에서 색인으로도 사용 IndexerList<T>
되므로 중복 ID 또는 색인을 피하십시오 -그것이 내가 좋아하는 것입니다! 색인 / ID는 임의의 숫자 일 수 있습니다.
실용적인 예는 다음과 같습니다.
http://www.ytechie.com/2009/02/using-c-yield-for-readability-and-performance.html
표준 코드보다 yield를 사용하면 여러 가지 장점이 있습니다.
그러나 Jan_V가 말했듯이 (몇 초 만에 나를 이겼습니다 :-) 내부적으로 컴파일러는 두 경우 모두 거의 동일한 코드를 생성하기 때문에 그것 없이도 살 수 있습니다.
예를 들면 다음과 같습니다.
https://bitbucket.org/ant512/workingweek/src/a745d02ba16f/source/WorkingWeek/Week.cs#cl-158
수업은 주중에 날짜 계산을 수행합니다. 밥이 12시 30 분에 점심 시간으로 쉬는 시간으로 밥이 매주 9:30부터 17:30까지 일한다고 수업의 한 예를 말할 수 있습니다. 이 정보를 바탕으로 AscendingShifts () 함수는 제공된 날짜 사이에 작업 시프트 오브젝트를 생성합니다. 올해 1 월 1 일에서 2 월 1 일 사이에 Bob의 모든 근무 교대를 나열하려면 다음과 같이 사용하십시오.
foreach (var shift in week.AscendingShifts(new DateTime(2011, 1, 1), new DateTime(2011, 2, 1)) {
Console.WriteLine(shift);
}
클래스는 실제로 컬렉션을 반복하지 않습니다. 그러나 두 날짜 간의 이동은 컬렉션으로 생각할 수 있습니다. yield
연산자 가능 수집 자체를 만들지 않고 상상이 컬렉션을 반복 할 수있다.
command
SQL 명령 텍스트, 명령 유형을 설정하고 IEnumerable 'command parameters'를 반환하는 클래스 가있는 작은 db 데이터 계층이 있습니다 .
기본적으로 아이디어는 SqlCommand
항상 수동으로 속성과 매개 변수를 채우는 대신 CLR 명령을 입력 하는 것입니다.
따라서 다음과 같은 기능이 있습니다.
IEnumerable<DbParameter> GetParameters()
{
// here i do something like
yield return new DbParameter { name = "@Age", value = this.Age };
yield return new DbParameter { name = "@Name", value = this.Name };
}
이 상속 클래스 command
클래스는 특성을 가지고 Age
와 Name
.
그런 다음 command
속성으로 채워진 객체를 새로 만들어 db
실제로 명령 호출 을 수행하는 인터페이스로 전달할 수 있습니다.
대체로 SQL 명령으로 작업하고 입력을 유지하는 것이 정말 쉽습니다.
합병 사례가 이미 수용된 답변에서 다루어졌지만 yield-merge params extension method ™를 보여 드리겠습니다.
public static IEnumerable<T> AppendParams<T>(this IEnumerable<T> a, params T[] b)
{
foreach (var el in a) yield return el;
foreach (var el in b) yield return el;
}
이것을 사용하여 네트워크 프로토콜의 패킷을 만듭니다.
static byte[] MakeCommandPacket(string cmd)
{
return
header
.AppendParams<byte>(0, 0, 1, 0, 0, 1, 0x92, 0, 0, 0, 0)
.AppendAscii(cmd)
.MarkLength()
.MarkChecksum()
.ToArray();
}
MarkChecksum
방법은, 예를 들어, 다음과 같습니다. 그리고 그것도 yield
있습니다 :
public static IEnumerable<byte> MarkChecksum(this IEnumerable<byte> data, int pos = 6)
{
foreach (byte b in data)
{
yield return pos-- == 0 ? (byte)data.Sum(z => z) : b;
}
}
그러나 열거 메서드에서 Sum ()과 같은 집계 메서드를 사용할 때는 별도의 열거 프로세스를 트리거하므로주의하십시오.
Elastic Search .NET 예제 repo에는 yield return
컬렉션을 지정된 크기의 여러 컬렉션으로 분할하는 데 사용하는 훌륭한 예제가 있습니다.
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int size)
{
T[] array = null;
int count = 0;
foreach (T item in source)
{
if (array == null)
{
array = new T[size];
}
array[count] = item;
count++;
if (count == size)
{
yield return new ReadOnlyCollection<T>(array);
array = null;
count = 0;
}
}
if (array != null)
{
Array.Resize(ref array, count);
yield return new ReadOnlyCollection<T>(array);
}
}