לדלג לתוכן

פתרון חיי סגירות וסדרות

ה־finally שייך לחיי ה־enumerator, וה־using החיצוני שייך לחיי ה־reader. ההפרדה מאפשרת לצרכן להמשיך לקרוא אחרי שסיים מעבר חלקי.

using var reader = new StringReader("first\nsecond\n");
foreach (string line in Lines(reader))
{
    Console.WriteLine(line);
    break;
}
Console.WriteLine(reader.ReadLine());

static IEnumerable<string> Lines(TextReader reader)
{
    try
    {
        string? line;
        while ((line = reader.ReadLine()) is not null) yield return line;
    }
    finally { Console.WriteLine("enumerator disposed"); }
}

המינוי הבא שומר את זהות ה־handler ומנתק אותה פעם אחת. הקוד הוא הגדרת טיפוסים לצירוף לאותה תוכנית, והבדיקה תיצור Publisher ותפעיל Raise לפני Dispose ואחריו.

sealed class Publisher
{
    public event Action? Tick;
    public void Raise() => Tick?.Invoke();
}
sealed class Subscriber : IDisposable
{
    private Publisher? publisher;
    private readonly Action handler;
    private readonly byte[] buffer = new byte[1_000_000];
    public int Calls { get; private set; }
    public Subscriber(Publisher publisher)
    {
        this.publisher = publisher;
        handler = OnTick;
        publisher.Tick += handler;
    }
    private void OnTick() { Calls++; buffer[0]++; }
    public void Dispose()
    {
        if (publisher is null) return;
        publisher.Tick -= handler;
        publisher = null;
    }
}

הגרף שלפני הניתוק עובר מן המפרסם אל delegate שה־Target שלו הוא Subscriber. אחרי הניתוק ה־publisher אינו מחזיק אותו, אם אין references אחרים. בתיקון הלולאה נוסיף int captured = i בתוך גוף הלולאה וניצור () => captured. ההעתקה של רשימת delegates אינה משכפלת את תאי הסגירה שלהם, ולכן היא אינה משנה את התוצאה בעצמה.