거의 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연산자 가능 수집 자체를 만들지 않고 상상이 컬렉션을 반복 할 수있다.
commandSQL 명령 텍스트, 명령 유형을 설정하고 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);
}
}