25 Haziran 2020 Perşembe

Yield - Code Generator

Yield Nedir?
Yield bir optimizasyondan ibaret. Lazy enumeration yapabilmeyi sağlıyor. Sadece IEnumerable, IEnumerator, IEnumerable<T> veya IEnumerator<T> döndüren metodlarda kullanılabilir.

Neden Generator Code Lazım
Açıklaması şöyle. Aslında okuması daha zor ancak Lazy enumeration varsa ve bir container nesneyi doldurmak daa pahalıya geliyorsa kullanılabilir.
Generators vs containers Use your best judgement, bearing in mind: Generator code is often less readable than filling in a container. Generator code can be more performant if the results are going to be processed lazily, e.g. when not all the results are needed. Generator code that is directly turned into a container via ToList() will be less performant than filling in a container directly. Generator code that is called multiple times will be considerably slower than iterating over a container multiple times.

Kapatmak
IEnumerable Linq tarafından kapatılır. Yani finally bloku içindeki kod işletilir.
Örnek
Şöyle yaparız.
IEnumerable<string> Foo()
{
  try
  {
    // open a network connection, start reading packets
    while(moredata)
    {
      yield return packet; 
    }
  }
  finally
  {
    // close connection 
  }
}
Örnek
Elimizde şöyle bir kod olsun.
private IEnumerable<string> Test()
{
  using (TestClass t = new TestClass())
  {
    try
    {
      System.Diagnostics.Debug.Print("1");
      yield return "1";
      System.Diagnostics.Debug.Print("2");
      yield return "2";
      System.Diagnostics.Debug.Print("3");
      yield return "3";
      System.Diagnostics.Debug.Print("4");
      yield return "4";
    }
    finally
    {
      System.Diagnostics.Debug.Print("Finally");
    }
  }
}

private class TestClass : IDisposable
{
  public void Dispose()
  {
    System.Diagnostics.Debug.Print("Disposed");
  }
}
Bu kodu iki farklı şekilde kullanalım.
foreach (string s in Test())
{
  System.Diagnostics.Debug.Print(s);
  if (s == "3") break;
}

string f = Test().First();
Çıktı olarak şunu alırız
1
1
2
2
3
3
Finally
Disposed
1
Finally
Disposed
Finally blokundaki kod'un çalıştığı görülebilir. Şimdi kullanım örneklerine bakalım

Tüm Listeyi Kopyalamamak - Elimizde bir liste olması gerekir.
IEnumerable döndüren bir metodumuz varsa tüm listeyi döndürmek yerine yavaş yavaş döndürmek için kullanılır.

Örnek
Eskiden şöyle yapardık.
IEnumerable<int> DoSomething(IEnumerable<int> coll)
{
  var collection = new List<int>();
  foreach (var i in coll)
  {
    collection.Add(i);
  }
  return collection;
}
Yield ile şöyle yaparız.
IEnumerable<int> DoSomething(IEnumerable<int> coll)
{
  foreach (var i in coll)
  {
    yield return i;
  }
}
Neden listeyi kopyalamamak isteriz? Çünkü bazen listeler gerçekten büyük olabiliyorlar.
// Method returns all million items before anything can loop over them. 
List<object> GetAllItems() {
  List<object> millionCustomers;
  database.LoadMillionCustomerRecords(millionCustomers); 
  return millionCustomers;
}
Zaten büyük bir listeyi kopyalamak yerine üzerinden dönmek çok daha avantajlı olabilir.
// 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];
}
Örnek
Bir predicate true olduğu müddetçe listeyi dolaşmak için şöyle yaparız.
public static IEnumerable<S> AggregatingTakeWhile<S, A>(this IEnumerable<S> items,
  A initial,
  Func<A, S, A> accumulator,
  Func<A, S, bool> predicate) 
{
  A current = initial;
  foreach(S item in items)
  {
    current = accumulator(current, item);
    if (!predicate(current, item))
      break;
    yield return item;
  }
}
Çağırmak için şöyle yaparız.
var items = myObjList.AggregatingTakeWhile(
  0,
  (a, s) => a + s.MyValue,
  (a, s) => a <= 5);
İki Listeyi Birleştirmek - Elimizde iki liste olması gerekir
İki listeyi birleştirmek için geçici bir liste oluşturmaktan kurtarıyor. Şöyle yaparız.
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;
}
Biz Dizi Üretmek
Örnek
Şöyle yaparızz
public IEnumerable<string> GetHelloWorld()
{
  yield return "Hello";
  yield return "World";
}
Örnek
Şöyle bir dizi üretmek isteyelim. Yani sayılar 2 artarak gitsin ve bir artı bir de eksi sayı olsun.
[1, -3, 5, -7, 9, -11, ...]
Şöyle yaparız
public static IEnumerable<int> AlternatingSequence()
{
  var i = 1;
  yield return i;
  var b = false;
  while (true) yield return ((b = !b) ? -1 : 1) * (i = i + 2);
}
Bu diziyi 500 bin defa çağırıp PI sayısını hesaplamak için şöyle yaparız.
public static double EstimatePI(int sumLength) 
{
  return (4 * AlternatingSequence().Take(sumLength).Sum(x => 1.0 / x));
}

var pi = EstimatePI(500_000).Dump(); // 3,14159065358969

Hiç yorum yok:

Yorum Gönder