Category Archives: Patterns & Practices

ASP.NET Core – Dependency Injection & Unit-testing – záznam, slides a dema [WUG Days Brno 10/2016]

5lides a dema z mé přednášky pro WUG Days Brno ze 8.10.2016:

Záznam z přednášky je publikován na našem HAVIT YouTube Channel.

 

ASP.NET Core – Dependency Injection & Unit-testing – záznam, slides a dema [TechEd 05/2016]

5lides a dema z mé přednášky pro TechEd DevCon Praha ze 17.5.2016:

Záznam z přednášky je publikován na našem HAVIT YouTube Channel.

Dotčená témata

  • ASP.NET Core
  • Dependency Injection základy
  • Startup.cs – ConfigureServices + environment specific
  • Controller – constructor injection
  • View – property injection
  • Unit-testing v ASP.NET Core
  • Mocking
  • Moq

[ASP].NET Worst Practices – záznam, slides a dema [Prague .NET Meetup 02/2016]

Slides a dema z mé přednášky pro setkání Prague .NET Meetup ze 17.2.2016:

Záznam z přednášky je publikován na našem HAVIT YouTube Channel.

Dotčená témata

  • Garbage Collector, Finalization, C# Destructors, Resource Wrapper pattern
  • Process Failure, StackOverflow, Windows Debugger, DebugDiag
  • <compilation debug=“true|false“ />, <deployment retail=“true“/>
  • ASP.NET Over-posting / Mass-assignment
  • skládání stringů vs. StringBuilder
  • vyhledávání v datech – List vs. BinnarySearch vs. Dictionary vs. LINQ ToLookup()
  • perly code-review

Code Analyzers vs. generovaný kód

Pomocí analyzérů ve VS 2015 máme možnost kontrolovat programový kód různými pravidly ověřující štábní kulturu kódu. Netestuje se tedy, zda kód dělá to, co má, ale zejména to, jak je zapsán. Ale i to dokáže občas odhalit chyby v chování programu.
V aplikacích je však nejen kód ručně psaný programátory, ale často i kód generovaný různými nástroji (designer soubory od resources, aspx, atp.)

Takový kód často nesplňuje pravidla, které na vlastní kód klademe, a zároveň nemáme možnost způsob generování ovlivnit.

Jedno možností je použít global suppressions (viz Code Analysis na stránce https://www.visualstudio.com/en-us/news/vs2015-update1-vs.aspx), druhou možností je vypnout kontrolu generovaného kódu ve vlastnostech projektu.

codeanalysis

To však přináší zmatek v tom, co který analyzér považuje za generovaný kód. Pro určení, zda jde o generovaný kód používají různé nástroje různé techniky, například

  • dle pojmenování souboru
  • dle obsahu souboru – komentář s <auto-generated />
  • nebo dle obsahu souboru – attributy u třídy

StyleCop Analyzers

Jako generovaný kód označují kód souboru (zdroj):

  • jehož název končí na .designer.cs
  • nebo obsahuje v komentáři text <auto-generated či <autogenerated
  • nebo je soubor prázdný.

SonarLint for Visual Studio 2015

Jako generovaný kód označují kód (zdroj):

  • souboru, jehož název obsahuje .g., .generated., .designer., .generated., _generated. nebo temporarygeneratedfile_
  • nebo obsahuje v komentáři text <auto-generated či <autogenerated
  • nebo je třída odekorována atributem DebuggerNonUserCode, GeneratedCode, ExcludeFromCodeCoverage či CompilerGenerated.

Code Analyzers

Neobsahují žádnou logiku pro odlišení generovaných souborů od ostatních (nebo se mi ji alespoň v čase, který jsem hledání byl ochoten věnovat, žádnou nenašel).

(zdroj)

Srovnání analyzérů

Tabulka ukazuje, které analyzéry považují jaký soubor za generovaný (ano = generovaný, ne = nerozpoznáno):

.designer.cs
(název souboru)
// <auto-generated>
(obsah souboru)
[GeneratedCode]
(obsah souboru)
Code Analyzers  ne ne ne
StyleCop Analyzers ano ano ne
Sonar Lint ano ano ano

Money – kouzlíme s generiky II.

Generika jsou dobrá nejenom pro kolekce. Že se dají velmi dobře využít i v jiných situacích, dnes předvedu na třídě Money  (diskuze, kdy má být Money třída a kdy je lepší struct není předmětem tohoto článku). Uvidíte, že jdou dělat i takové věci jako generické operátory.

Zadání problému

Chceme řešit imlementaci třídy Money, třídy reprezentující peněžní datový typ skládající se z částky (Amount) a její měny (Currency). Naším cílem je dosáhnout následujího:

  • chceme zavést opakovaně využitelnou třídu Money (typicky do vlastní class-library) – ta má obsahovat vlastnost Amount (částku typu decimal) a vlastnost Currency (měnu), jejíž typ obecně naznáme a chceme ho ponechat na libovůli klientské aplikace (známe však pravidla pro obecné Money, které nám říkají, že bez kurzových převodů můžeme běžné operace provádět jen mezi operandy stejné měny – porovnávání, sčítání/odečítání, násobení/dělení číslem, dělení mezi sebou, atp.). Do class-library tedy chceme implementovat třídu Money<TCurrency>.
  • implementaci veškeré logiky chceme mít součástí této třídy Money<TCurrency> – v této třídě chceme definovat potřebné operace a centrálně je udržovat v class-library, chceme mít pokryté všechny základní operátory, atp. atp.
  • v jednoduchých svých projektech zadefinujeme třídu Currency (obvykle business-object odpovídající číselníku měn v DB s vlastnostmi Nazev, Zkratka, atp.) a budeme v nich třídu Money<TCurrency> používat přímo v kódu
Money<Currency> cena = new Money(150.20M, Currency.Czk);
cena = cena + new Money(20M, Currency.Czk);
cena = cena * 10;
MyLabel.Text = cena.Amount.ToString("n2") + " " + cena.Currency.Zkratka;
...
  • v rozsáhlejších našich projektech máme také třídu Currency, chceme však vytvořit odvozenou negenerickou třídu Money, která bude základní funkčnost obecnějšího Money<TCurrency> rozšiřovat o nějaké nové project-specific možnosti, např. ony kurzové přepočty, standardní výstup, atp. Použití zamýšlíme nějak takto:
Money cena = new Money(10M, Currency.Czk);
cena = cena.ToCurrency(Currency.Usd) + new Money(159.3M, Currency.Usd);
MyLabel.Text = cena.ToString("n2");
...
  • stále však trváme na tom, že implementace běžných operací (zejména operátorů) musí být použita z obecného Money<TCurrency>. Uvědomme si však základní problém – jak se vyhnout nové definici operátorů v negenerickém potomkovi Money : Money<Currency>, když operátory jsou statické, nejsou předmětem dědičnosti a v Money<TCurrency> budou vypadat nejspíš nějak takto:
public static Money<TCurrency> operator +(Money<TCurrency> money1, Money<TCurrency> money2)
{
// implementace
}

…v potomkovi Money chceme přeci součet dvou Money a hlavně návratový typ Money, nikoliv Money<Currency>

public static Money operator +(Money money1, Money money2)
{
// znovu implementace??? Tomu jsme se chtěli vyhnout! Implementaci chceme mít v class-library.
}
Kouzlo první – generická implementace funkčnosti operátorů

Čeho chceme dosáhnout – mít centralizovanou implementaci funkčnosti operátorů tak, abychom ji mohli snadno udržovat pro všechny naše projekty. Kouzlo první spočívá v extrahování implementace operací do generických metod třídy Money<TCurrency>, kteréžto metody sama třída Money<TCurrency> ve svých operátorech využije, stejnětak jako je může využít libovolný potomek při definici svých typovaných operátorů.

Money<TCurrency>:

public static Money<TCurrency> operator +(Money<TCurrency> money1, Money<TCurrency> money2)
{
    return SectiMoney<Money<TCurrency>>(money1, money2);
}

public static TResult SectiMoney<TResult>(Money<TCurrency> money1, Money<TCurrency> money2)
    where TResult : Money<TCurrency>, new()
{
    if (money1.Currency != money2.Currency)
    {
        throw ...
    }

    TResult result = new TResult();
    result.Amount = money1.Amount + money2.Amount;
    result.Currency = money1.Currency;
    return result;
}

Obdobně jako u BusinessObjectCollection využíváme kouzla generika s „cílovým typem“. Daní za tento způsob je nám nutnost vyžadovat constructor bez parametrů, abychom v generiku byly schopni vytvořit instanci cílového typu (možnost generika s constructorem předepsaných parametrů se asi v dohledné době nedočkáme, v .NET 3.5 pokud vím, nic takového stále není a po diskuzi s částí CLR teamu na letošním MVP Summitu v Redmondu už i chápu proč).

Project-specific potomek Money už pak může využívat této centrální implementace snadno:

public static Money operator +(Money money1, Money money2)
{
    return SectiMoney<Money>(money1, money2);
}
Kouzlo druhé – projektový potomek Money bez nové implementace operátorů

Řekněme, že se chceme v projektech, v project-specific třídách Money, úplně zbavit nutnosti implementovat operátory, byť už máme cestu jak v těchto operátorech využít centrální implementace funkčnosti. Projektový kód chceme mít co nejčistší, prostý takovýchto infrastrukturních redefinic, tak, abychom se v něm mohli přehledně věnovat jen nové funkčnosti Money oproti předkovi Money<TCurrency> – např. doplnit ony omílané kurzové přepočty, atp.

Jak na to?

Do řetězce dědičnosti mezi Money a Money<TCurrency> vložíme kouzelnou třídu MoneyImplementationBase, která nás dalším fíglem s generiky a operátory posune ke kýženému výsledku…

public abstract class MoneyImplementationBase<TCurrency, TResult>: Money<TCurrency>
    where TCurrency: class
    where TResult : MoneyImplementationBase<TCurrency, TResult>, new()
{
    public static TResult operator +(MoneyImplementationBase<TCurrency, TResult> money1, MoneyImplementationBase<TCurrency, TResult> money2)
    {
        return SectiMoney<TResult>(money1, money2);
    }
}

Tato třída samozřejmě patří do naší centrální class-library odkud ji budou využívat jednotlivé projekty.

A výsledek? Plně funkční projektové Money pak dostaneme takto:

public class Money : MoneyImplementationBase<Currency, Money>
{
    // toť vše!
    // sem už můžeme doplnit jen project-specific rozšíření Money, veškerou základní funkčnost nám poskytují předci
}

Hezké, že? Stejně jako u kouzlení s BusinessObjectCollection předhazujeme generickému předkovi sami sebe (pokud to bude potřeba, můžeme předhodit i svého potomka).

BusinessObjectCollection – kouzlíme s generiky

Po opakovaných přáních poodkrýt zákoutí našeho frameworku a generované business-vrstvy jsem se rozhodl zveřejnit vybraná témata a perličky. Dnes přicházím s BusinessObjectCollection, resp. s možným způsobem implementace kolekcí v business-vrstvě pomocí generik tak, abychom dosáhli maximální kompatibility typů a nemuseli bojovat s neustálou typovou neshodou kolekcí.

Čeho chceme u kolekcí business-objektů dosáhnout:

  1. Chceme jednu společnou bázovou třídu BusinessObjectCollection pro všechny kolekce business-objektů a v ní centrálně implementovat operace, které jsou společné pro všechny business-objekty. Pro příklad tam dáme primitivní metodu SaveAll(), která všechny business-objekty v kolekci uloží a její implementace je stejná, ať už se jedná o kolekci Faktur, nebo kolekci Subjektů.
  2. V bázové třídě BusinessObjectCollection chceme definovat i operace, které pracují přímo s business-objekty, pokud možno strong-type, typem co nejpřesnějším. Např. běžné Add(Order order), Remove(Subject subject), atp.
  3. V bázové třídě BusinessObjectCollection chceme definovat i operace, které pracují s kolekcemi business-objektů, pokud možno strong-type, typem kolekce co nejpřesnějším. Např. FindAll(), který vrací kolekci všech nalezených prvků odpovídajících nějaké podmínce, atp.
  4. Od této společné bázové abstraktní třídy BusinessObjectCollection chceme děděním vytvářet konkrétní potomky – třídy kolekcí pro jednotlivé business-objekty, např. OrderCollection, SubjectCollection, UserCollection, atp.

Jak tedy na to? Nejprve si zavedeme základní třídy, které budu v dalších příkladech používat:

public abstract class BusinessObjectBase
{
    public abstract void Save();
}

public class Order : BusinessObjectBase
{
    public decimal Cena { get; set; }
    public override void Save()
    {
        // implementace
    }
}

BusinessObjectBase je bázová třída pro všechny business-objekty, Order je příklad konkrétní implementace.

Způsob 1. – základní

Nejprve si ukážeme, jak vypadá běžná implementace kolekcí:

public abstract class BusinessObjectCollection<T> : Collection<T>
    where T : BusinessObjectBase
{
    public BusinessObjectCollection()
        : base(new List<T>())
    {
    }

    public void SaveAll()
    {
        foreach (BusinessObjectBase item in this.Items)
        {
            item.Save();
        }
    }

    public virtual List<T> FindAll(Predicate<T> match)
    {
        List<T> innerList = (List<T>)Items;
        List<T> found = innerList.FindAll(match);
        return found;
    }
}

public class OrderCollection : BusinessObjectCollection<Order>
{
    public decimal GetCelkovaCena()
    {
        decimal result = 0;
        // implementace
        return result;
    }
}

Co tato implementace ukazuje:

  • Jako úplný základ používáme generickou kolekci Collection<T>, která samotná už nám přináší všechny základní strong-type operace, např. Add(), Contains(),IndexOf(), Insert(), Remove(), RemoveAt() a indexer přes pořadí this[int].
  • Třída Collection<T> je implementačně pouze wrapperem nějaké vnitřní kolekce, kterou skrývá pod property protected IList<T> Items. Vnitřní implementaceCollection<T> používá jako tento datový nosič List<T>, v kontraktu této třídy to však popsáno není. Chceme-li tedy zaručit, že naše prvky budou fyzicky uloženy v datové struktuře typu List<T>, můžeme využít protected constructor odCollection<T>, který umožňuje předat instanci požadované datové struktury, která musí implementovat IList<T>. Nám se hodí právě List<T>, který zahrnuje spoustu dalších užitečných metod, které budeme chtít naše business-kolekce také naučit. V příkladu využíváme List<T>.FindAll().
  • Metoda SaveAll() provádí určitou operaci s prvky kolekce, její interface je vyhovující, není co namítat.
  • Metoda FindAll() ale už není tak krásná, jejím návratovým typem je totiž List<T> a tím se nám klientský kód pěkně zamotá:
OrderCollection orders = Order.GetAll();

List<Order> zeroOrders = orders.FindAll(delegate(Order item)
{
    return (item.Cena == 0);
});

zeroOrders.SaveAll(); //nejde
decimal d = zeroOrder.GetCelkovaCena(); //nejde
OrderCollection zeroOrders2 = (OrderCollection)zeroOrders; // nejde

Návratovým typem FindAll() je List<Order>, s kterým přicházíme o veškerou logiku, kterou jsme business-kolekce naučili, jak v bázové třídě BusinessObjectCollection, tak v konkrétní tříděOrderCollection.

Způsob 2. – Drobné vylepšení základní metody

Zlepšení na poli typů dosáhneme, pokud implementaci naší BusinessObjectCollection<T> upravíme takto:

public abstract class BusinessObjectCollection<T> : Collection<T>
    where T : BusinessObjectBase
{
    public BusinessObjectCollection()
        : base(new List<T>())
    {
    }

    public void AddRange(IEnumerable<T> source)
    {
        List<T> innerList = (List<T>)Items;
        innerList.AddRange(source);
    }

    public virtual BusinessObjectCollection<T> FindAll(Predicate<T> match)
    {
        List<T> innerList = (List<T>)Items;
        List<T> found = innerList.FindAll(match);

        BusinessObjectCollection<T> result = new BusinessObjectCollection<T>();
        result.AddRange(found);
        return result;
    }

    public void SaveAll()
    {
        foreach (BusinessObjectBase item in this.Items)
        {
            item.Save();
        }
    }
}
  • Metodu FindAll() jsme trošku typově vylepšili, namísto obecného List<T> ji necháme vracet alespoň datový typ BusinessObjectCollection<T>. Protože však musíme instanci BusinessObjectCollection<T> nějak získat, nezbývá než si založit novou a pomocí další vypropagované metody List<T>.AddRange() do ní prvky zkopírovat. Je to nepochybně určitý výkonový overhead, nicméně vzhledem k tomu, že kopírujeme pouze reference na existující instance jednotlivých business-objektů, jedná se o overhead zanedbatelný a naopak převáží výhoda příjemného vylepšení interface kolekcí.
  • Stále však nemůžeme být spokojeni:
OrderCollection orders = Order.GetAll();

BusinessObjectCollection<Order> zeroOrders = orders.FindAll(delegate(Order item)
{
    return (item.Cena == 0);
});

zeroOrders.SaveAll(); //HURÁ! Funguje!
decimal d = zeroOrders.GetCelkovaCena(); // stále nefunguje
OrderCollection zeroOrders2 = (OrderCollection)zeroOrders; // nejde

Stále nedostáváme od metody FindAll() kolekci typu OrderCollection. Sice jsme získali možnost využít operací implementovaných v BusinessObjectCollection, jako např. SaveAll(), stále však nedosáhneme na operace implementované v OrderCollection, nemáme GetCelkovaCena().

Způsob 3. – Kouzlíme s generiky

Generika v .NET Frameworku jsou udělána dobře a snesou všechno. Opakovaně jsem sám u generik předpokládal, že „tohle už přeci nemůže jít“. Generika opravdu unesou hodně:

public abstract class BusinessObjectCollection<TItem, TCollection> : Collection<TItem>
    where TItem : BusinessObjectBase
    where TCollection : BusinessObjectCollection<TItem, TCollection>, new()
{
    public BusinessObjectCollection()
        : base(new List<TItem>())
    {
    }

    public void AddRange(IEnumerable<TItem> source)
    {
        List<TItem> innerList = (List<TItem>)Items;
        innerList.AddRange(source);
    }

    public virtual TCollection FindAll(Predicate<TItem> match)
    {
        List<TItem> innerList = (List<TItem>)Items;
        List<TItem> found = innerList.FindAll(match);

        TCollection result = new TCollection();
        result.AddRange(found);
        return result;
    }

    public void SaveAll()
    {
        foreach (BusinessObjectBase item in this.Items)
        {
            item.Save();
        }
    }
}

public class OrderCollection : BusinessObjectCollection<Order, OrderCollection>
{
    public decimal GetCelkovaCena()
    {
        decimal result = 0;
        // implementace
        return result;
    }
}

Co jsme to udělali?

  • Definici třídy BusinessObjectCollection jsme rozšířili o další generický typ naBusinessObjectCollection<TItem, TCollection>, kde TItem je typ prvků kolekce aTCollection je typ kolekce samotné!
  • Pomocí generického typu TCollection můžeme nyní postavit metodu FindAll tak, že jejím návratovým typem bude TCollection, tedy požadovaný typ kolekce.
  • Získáváme plně typově konzistentní kolekci OrderCollection, jejíž všechny metody používají typ Order jako typ prvku a typ OrderCollection jako typ kolekce!
  • Dostáváme jednoduchý snadno použitelný interface:
OrderCollection orders = Order.GetAll();

OrderCollection zeroOrders = orders.FindAll(delegate(Order item)
{
    return (item.Cena == 0);
});

zeroOrders.SaveAll(); //HURÁ! Funguje!
decimal d = zeroOrders.GetCelkovaCena(); //HURÁ! Funguje!
// přetypovávat už ani nepotřebujeme
Způsob 4. – A jak je to u nás?

Generika snesou hodně, opravdu hodně. U nás je to takto:

  • V našich class-libraries (HAVIT Framework Extensions) máme abstraktní předkyBusinessObjectBase a BusinessObjectCollection<TItem, TCollection>. Tyto třídy definují operace společné pro všechny business-objekty, na všech našich projektech, prostě úplný základ, jádro.
  • Na konkrétních projektech pak používáme generování základního kódu business-tříd, tedy business-objektů i business-kolekcí, na základě vstupního schematu (např. DB diagramu). Generovaný kód umísťujeme do bázových tříd OrderBase aOrderCollectionBase, zatímco vlastní kód zapisujeme až do potomků těchto třídOrder a OrderCollection.
  • Při prvním spuštění generátor vygeneruje třídy všechny (třídy Order aOrderCollection vygeneruje prázdné), zatímco při dalších spuštěních generátor přepisuje třídy OrderBase a OrderCollectionBase (náš „dopsaný“ kód tak zůstává nedotčen).
  • Výsledný kód tříd vypadá nějak takto:

Nejprve business-objekt Order:

public abstract class OrderBase : BusinessObjectBase
{
    // toto je generovaná třída, generátor ji vždy přepíše, nic se sem nesmí ručně dopisovat
    public decimal Cena { get; set; }
    public override void Save()
    {
        // implementace
    }
}

public class Order : OrderBase
{
    // toto je používaná třída, sem mohu zapsat vlastní kód

    public decimal GetCenaPoSleve()
    {
        // implementace
    }
}

Dále business-kolekce OrderCollection:

public abstract class OrderCollectionBase : BusinessObjectCollection<Order, OrderCollection>
{
    // toto je generovaná třída, generátor ji vždy přepíše, nic se sem nesmí ručně dopisovat
}

public class OrderCollection : OrderCollectionBase
{
    // toto je používaná třída, sem mohu zapsat vlastní kód
    public decimal GetCelkovaCena()
    {
        decimal result = 0;
        // implementace
        return result;
    }
}

Ano! Generika umožňují dokonce v předkovi OrderCollectionBase používat jako TCollection odkaz na vlastního potomka OrderCollection.

Pokračování

IDisposable – ResourceWrapper design pattern

IDisposable je interface, který předepisuje implementovat jednu jedinou metodu:

public interface IDisposable
{
    void Dispose ();
}

Metoda Dispose() slouží k uvolnění unmanaged resources (file handles, window handles, database connections, atp.) a celé rozhraní IDisposable je tak určeno k implementaci tzv. Explicit Resource Managementu, tedy chceme dát programátorům používajícím naší třídu možnost explicitního úklidu unmanaged resources v okamžiku, kdy instanci naší třídy přestanou dále potřebovat:

Resource r = new Resource();
try
{
  r.DoSomething();
}
finally
{
  if (r != null)
  {
    ((IDisposable)r).Dispose();
  }
}

Tento zápis lze v C# ekvivalentně zkrátit pomocí statementu using:

 

using (Resource r = new Resource())
{
    r.DoSomething();
}

Statement using je určen pro přehlednou práci s IDisposable třídami a ve skutečnosti to není nic jiného, než výše uvedený try-finally blok.

ResourceWrapper design pattern

Microsoft v .NET Frameworku sám většinou používá a pro explicitní management resources doporučuje tzv. ResourceWrapper design pattern. Jeho funkčnost vychází z následujích požadavků:

  1. Primárně by měli programátoři používající naší třídu uvolňovat unamanaged resources právě prostřednictvím volání metody IDisposable.Dispose(), popř. pomocí další metody očekávanějšího jména stejné funkčnosti – např. file.Close().
  2. Pokud nedejbože nepozorný programátor zapomene explicitně zavolat úklid metodou Dispose(), postaráme se o úklid a uvolnění resources alespoň v destruktoru třídy (C# destruktory jsou ve skutečnosti překládány do metody Finalize()).

Důležité je však zdůraznit, že zatímco metodu Dispose() můžeme volat ihned, jakmile přestaneme příslušnou instanci potřebovat, a tedy dojde k uvolnění zdrojů hned, destruktor je volaný až po garbage collection, která může proběhnout taky až za velmi dlouhou dobu a do té doby jsou všechny unmanaged resources neuklizeny, např. tedy blokujeme přístup k nějakému souboru, s nímž zbytečně dlouho nemůže nikdo jiný pracovat.

Dále je potřeba si uvědomit, jak Garbage Collector pracuje s instancemi tříd, které mají destructor (metodu Finalize):

  1. Runtime si udržuje seznam všech existujících objektů, které budou při svém odstraňování potřebovat finalizaci (mají destruktor) – tzv. Finalization queue.
  2. Garbage Collector při úklid prochází graf dostupných objektů od objektů kořenových (statické globální proměnné, lokální proměnné v zásobníku, registry procesoru) a objekty, které jsou nedostupné, ty uklízí – uvolňuje je z paměti.
  3. Pokud však Garbage Collector má uklidit objekt, který potřebuje finalizaci (je ve Finalization Queue, má destruktor), pak je neuvolní hned, nýbrž je vyjme z Finalization Queue a vytvoří jim záznam v tzv. Freachable queue (finalization-reachable). Freachable queue je součástí kořenových objektových struktur a objekty v ní umístěné jsou tak znovu dostupné (reachable) – Garbage Collector je neuklidí.
  4. Freachable queue existuje proto, že jednotlivé destuktory mohou trvat i velmi dlouho a není tak vhodné, aby volání destruktorů zdržovalo samotný běh Garbage Collectoru. Proto GC hází objekty, které potřebují finalizaci do Freachable queue a sám běží dál/skončí. Frontu Freachable obsluhuje samostatný finalizační thread, který z ní vybírá jednotlivé objekty a provádí jejich finalizaci (volá metodu Finalize, tj. destruktory).
  5. Provedením metody Finalize (destruktoru) finalizační thread vyjme objekt z Freachable queue a ten už tak není ani dostupný přes tuto frontu, ani není v hlavním seznamu objektů pro finalizaci, a proto může být Garbage Collectorem jako nedostupný konečně uklizen.
  6. Protože se objekty s destruktory stávají po dobu umístění v Freachable queue opět dostupnými, stávají se tak dostupnými i všechny objekty, které ten samotný objekt refrencuje, tj. celý podstrom objektů, přestože ony samy třeba destruktory nemají. Řádné uvolnění paměti je tak odsouváno, oproti běžnému průběhu GC nad objekty bez finalizace (bez destruktorů).
  7. Poslední záludností Freachable queue je, že objekty v ní nejsou umístěny v pořadí, v jakém byly založeny, ale v pořadí, v jakém se k jejich zpracování dostal Garbage Collector, tedy prakticky v pořadí nijak nedefinovaném. V našem destruktoru tedy nevíme, jestli destruktory objektů, které sami referencujeme, už proběhly (objekty byly ve frontě před námi), nebo jestli teprve proběhnou (objekty jsou ve frontě za námi).

Co z toho vyplývá pro implementaci:

  1. Pokud uživatel uvolní prostředky explicitně voláním metody Dispose(), pak chceme potlačit volání pojistné finalizace (destruktoru), protože finalizace odsouvá uvolnění objektu i objektů jím referencovaných. To budeme dělat použitím metody GC.SuppressFinalize(), která vyjímá objekt z Finalization Queue a objekt tak není při garbage collection už přesouván do Freachable queue, nýbrž je uvolněn ihned.
  2. Zatímco v metodě Dispose() můžeme uklízet i další námi vlastněné IDisposable objekty, protože víme, že jejich úklid ještě neproběhl (pokud je máme private/protected jen pro sebe), tak v destruktoru to možné není. Destruktory jsou totiž volány v nedefinovaném pořadí a úklid námi vlastněných podřízených IDisposable objektů už mohl proběhnout před námi.

Jak to tedy všechno uděláme:

  1. Abychom byla naše třída imunní vůči vícenásobnému volání Dispose(), a abychom sami pro další kontroly věděli, jestli už disposování proběhlo, zavedeme si proměnnou bool disposed:
    private bool disposed = false;
    

  2. Protože chceme mít implementaci uvolňování prostředků na jednom místě, a protože chceme umožnit svým případným potomkům rozšířit Dispose(), zavedeme si metodu protected virtual void Dispose(bool disposing), kde parametr disposing identifikuje, jestli samotné volání této implementační metody proběhlo z explicitního Dispose() a můžeme tak uvolňovat i další vlastněné IDisposable členy (disposing == true), nebo jestli volání proběhlo z destruktoru a můžeme uklízet jen sami sebe (disposing == false).
protected virtual void Dispose(bool disposing)
{
  // Dispose() má být imunní vůči vícenásobnému volání
  if(!this.disposed)
  {
    // Pokud jsme volání z metody Dispose(),
    // můžeme uvolnit i vlastněné IDisposable prvky. Z destruktoru ne.
    if(disposing)
    {
      // Uvolňujeme tedy tzv. managed resources.
      component.Dispose();
    }
         
    // ...v každém případě však uvolňujeme unmanaged resources 
    CloseHandle(handle);
    handle = IntPtr.Zero;            
    disposed = true; 
  }
}

3. Do samotné metody IDisposable.Dispose() pak dáme volání této implementace (s parametrem disposing = true) a dále vyřadíme objekt z Finalization Queue, tedy potlačíme jeho další finalizaci. volání destruktoru (a umožníme tak rychlý úklid objektu Garbage Collectorem):

public void Dispose()   // popř. explicitně IDisposable.Dispose()
{
  Dispose(true);
  GC.SuppressFinalize(this);
}

4. Protože chceme zajistit uvolnění unmanaged prostředků i v případě, že programátor zapomene zavolat Dispose(), zavoláme metodu Dispose() i zdestruktoru:

~ResourceWrapper()
{
   Dispose(false);
}

5. Samotnou funkčnost třídy, která má být dostupná jen před disposováním objektu, pak podmíníme (!disposed):

public void DoSomething()
{
  if (disposed)
  {
    throw new ObjectDisposedException("ResourceWrapper");
  }

  // následuje vlastní funkčnost metody

}

6. Nakonec můžeme přidat ještě metodu s domain-specific názvem pro dispose:

public void Close()
{
  Dispose();
}
Související