2009. december 26., szombat

Community Powa

Kerestem fülbe dugható fülhallgatót. Szerencse, hogy az Interneten számos ár-összehasonlító oldal létezik, így a keresgélés jóval egyszerűbb - ráadásul a legtöbb helyen pontozni, kommentezni is lehet az egyes termékeket, így már vásárlás előtt teljes képet kaphat a megvásárolni kívánt portákáról. Találtam is egy elég szimpatikus darabot, a Sennheiser CX 300-as modelljét, nézzük, mit írnak róla!

"Ma vettem és elsőre letaglózott. ...10-es skálán 8-ast adnék neki, de így 5-ös skálán legyen 5-ös. Jó érték-arány, professzionális hangzás. Csak ajánlani tudom!"
Na, hát ez kiváló, bár a 10-es skálán 8-as eredményt ötös skálán ötössé konvertálni elég furcsa, lehet, hogy inkább egy másik értékelő véleményére kéne hallgatni. Nézzük mit ír a következő!
"Egy olcsóbb Panasonic inear-t szerettem volna leváltani vele. Nem sikerült. Annak ellenére, hogy Sennheiser, a hangja borzalmas, egyszerűen sérti a fület. Mindenféle EQ beállítással kipróbáltam, de egyik sem jött be. A közepei üvöltenek, a magas is sok, mélye pedig egyáltalán nincs. Nagyot csalódtam a gyártóban."
Ajaj, hát ezek szerint ez mégsem annyira tökéletes. De várjunk csak, nézzük mit írnak még:
"A legjobb! Ez előtt kb. 20-30 fülest kipróbáltam, de egyik se váltotta be a hozzá fűzött reményeket... Már fél éve használom a CX300-at és bátran kijelenthetem, hogy ez a legjobb ár/érték téren. Gyönyörűen szól! A 25Hz-es szinuszhullámtól, amit ráadtam, azt hittem, hogy fölrobbantja a fejem."
Hát akkor mégis elég jó lehet, bár itt megint számolni azoknak az embereknek a beszámíthatósági faktorával, akik 25Hz-es szinuszt hallgatnak.
Jobb lenne valami zenei műveltséggel bíró személy tapasztalataira... de hoppá, hát itt van!
"zenélek már 16 éve,és ugyan abszolút hallásom nincs,de tökéletes zenei relatív hallásom van,ilyen képességek birtokába mondom erről a fülesről hogy egy minőségi darab.csak akik a sok rosszon éltek előtte,azoknak ez most túl erős /gyenge lehet attól függ ogy előtte milyen fülesük volt. érdemes az eq-val próbálkozni,kiválló hangzást lehet elérni vele."
Na, hát jó ez az írástudók szerint is. Akkor most már biztos, hogy jó lesz. Na még egy véleményt elolvasok...
"Egyszerüen nem szól tisztán. Kár, hogy megvettem. Kásás és a magas az szörnyű. Próba kell mindenképpen mielött megveszed, mert ha valaki erre 16 év zenélés után rámondja, hogy minőségi, akkor tényleg megosztja rendesen az embereket. Az ilyen vélemény maximum egy utalás arra aki zenéhez értőnek tartja magát."
Hajjaj, akkor most mégsem?
"Kitűnő hangzás, kitűnő dinamika, kitűnő kivitelezés! Ezzel lehetne jellemezni a CX300-at! Bődületesen jól szól! Mindenkinek csak ajánlani tudom!"
De, jó ez, kitűnő!
"Nulla a cucc! Sajnálom, hogy megvettem. Ami ebből szól, azt ne nevezzük hangzásnak."
Jajj, nem. Mégsem. Vagy...?
"A Sennheiser cx300 a legjobb ami valaha a fülemben volt!!! Leginkább a munkahelyemen használom a fülest, hogy "gyorsabban teljen az idő"."
Na, akkor most jó ez...
"Egy hifi üzletben volt lehetőségem vásárlás előtt meghallgatni. Ezek után a vásárlás elmaradt!"
Nem, mégsem, vagy...?

És így tovább, a végtelenségig. Műértők szakvéleményei, majd a műértők hozzá nem értését leleplező műértő írások, szépen, sorban, egymás alatt, a személyeskedő anyázás határmezsgyéjét még épp át nem lépve (amivel ez a fórum egyébként a kultúráltabbak közé pozícionálódik).
Ráadásul a kis füldugasz ára elég szép szórást mutat, 6.734 Ft-tól kerek 12.000 Ft-ig kapható, ami a következő, a széles nyilvánosság számára elérhető felületeken óhatatlanul felbukkanó kaszt, a beavatottak számára olyan magas labda, amit nem lehet nem lecsapni. Megtudhatjuk, hogy ezt a modellt hamisítják, sok helyen a boltokban is hamisítványt kapni, 8000 alatt biztos, afölött meg ki tudja, hát ki, ki más tudná, mint a nagybetűs FÓRUMOZÓ, csak szét kell bontani a borítást, és lesz ott egy szám, ami ha négyessel kezdődik, akkor eredeti, ha hatos, akkor hamis, és mint ilyen, kerülendő, sz*r, értéktelen szemét.

Végül az lett, hogy legutóbb, mikor a szokásos ünnepek előtti nagybevásárlást intéztem egy hyperben (fel kellett tölteni az égetettszesz-készleteket), leakasztottam a szögről egy Panasonic fülest, 1990 Ft, nem egy audiophile cucc, de a célnak megfelel, az Asian Dub Foundation egész meggyőzően dörren meg rajta reggel a négyeshatoson.
Mi lett volna velem a Közösség nélkül? Valószínű egy háromszor (esetleg négyszer, ötször, vagy hatszor) ilyen drága fülest tömködnék reggelente a fülembe, aztán gondolkozhatnék erősen, hogy most amit hallok, az tetszik-e nekem, vagy sem.

Community power, köszönöm!

2009. december 18., péntek

Kovariancia és kontravariancia C# 4.0-ban

Miről beszélünk?

C#-ban egy referencia típusú változó mindig a saját típusára, vagy az abból leszármazott típusra mutathat. Ez teljesen transzparens, amikor egy object referencián meghívsz egy metódust, mindegy, hogy a referencia tényleg egy object-re, string-re, Kiskutya-ra mutat-e. Ezzel valószínűleg mindenki találkozott már az első "Objektum-orientált programozás" órán, valamint azóta kb. egycsilliárdszor.
Nevezzük ezt mondjuk "egyes számú szabály"-nak.

Ha két típust (mondjuk T-t és U-t) egymáshoz hasonlítunk, az alábbi négy lehetőségből pontosan egy lesz igaz:

  • T nagyobb, mint U (OOP terminológiával: T őse U-nak - a System.Object őse a System.String-nek, tehát az object nagyobb, mint a string)
  • T kisebb, mint U (OOP terminológiával: T leszármazottja U-nak - a System.String a System.Object leszármazottja, tehát a string kisebb, mint az object)
  • T egyenlő U-val (OOP terminológiával: T és U ugyanaz a típus)
  • T-nek nincs kapcsolata U-val (OOP terminológiával: navajon?)

Vegyünk egy műveletet, ami a típusokkal mókol valamit, egy adott típusból egy másikat csinál valamilyen szabály alapján: T-ból T'-t, U-ból U'-t.

Hogyha a T' ugyanolyan relációban lesz U'-vel (a fenti négyből), mint T volt U-val, akkor a művelet kovariáns. Ha a művelet megfordítja a relációt (pontosabban: a kisebbséget-nagyobbságot megfordítja, az egyenlőséget és a "közömbösséget" változatlanul hagyja), akkor a művelet kontravariáns.

Érthetetlen, ugye? Nézzünk egy példát!

Adott két típus, mondjuk a System.Object és a System.String (közülük az object a "nagyobb"). Most vegyünk egy "műveletet", ami csinál valamit a típusokkal. Ilyen művelet például a "képezzünk T tömböt", vagy a "képezzünk T listát". Lesz tehát object[] (aka. T'), string[] (aka. U'), List<object> (legyen mondjuk T''), List<string> (logikusan U'') típusunk.

Milyen viszonban vannak ezek a típusok egymással? Az object[] nagyobb, mint a string[]? Hát nézzük meg, az egyes számú szabály értelmében, ha az, akkor értékül lehet neki adni:

// C# 1.0 kód
string[] stringArray = new string[] { "http://", "otperc", ".net" };
object[] objectArray = stringArray;

A C#-ban a tömbképzés már a nyelv legelső verziója óta kovariáns.

És a List<string> castolható List<object>-té?

// C# 2.0 kód
var stringList = new List<string> { "http://", "otperc", ".net" };
var objectList = (List<object>)stringList; // ez nem fordul!

Erre bizony még a nyelv 3.0-ás változatában is egy "Cannot convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<object>'" a fordító válasza.

Veszélyes utakon

De vajon ha a tömböknél meg tudták oldani a dolgot már az 1.0-ban, akkor miért nem ment a genericnél a másodiknál? Eric Lippert azt mondja erről: "az a mód, ahogy a C# támogatja a tömböknél a kovarianciát, broken.
Azért került be a CLR-be, mert a CLR tervezői képessé akarták tenni azt a Java(-szerű) nyelvek támogatására, és ehhez szükség volt rá. Aztán mi beraktuk a C#-ba, ha már a CLR-ben bent volt. Sokat vitatkoztunk ezen a döntésen, és ma már nem örülök neki túlságosan, hogy így alakult, de most már nem sok mindent tudunk csinálni.
"

Miről beszél Lippert? Lássunk egy példakódot:

private static void GetDataMethod(object[] objectArrayParam)
{
    foreach (var item in objectArrayParam)
    {
        Console.WriteLine(item);
    }
}

private static void SetDataMethod(object[] objectArrayParam)
{
    objectArrayParam[0] = new object();
}

static void Main(string[] args)
{
    string[] stringArray = new string[] { "http://", "otperc", ".net" };
    object[] objectArray = stringArray;

    GetDataMethod(objectArray);
    SetDataMethod(objectArray);
}

Az első metódus lefut, de a második futtatásakor egy "ArrayTypeMismatchException: Attempted to access an element as a type incompatible with the array." üzenetet kapunk. A hangsúly a futtatásakoron van: sikerült egy erősen típusos nyelvbe egy olyan konstrukciót beemelni, ami remek lehetőséget ad a compile-time típusbiztosság-ellenőrzés kikerülésére - amivel pedig megnyílnak a pokol kapui.

A helyes út

A C# 2.0-tól van olyan variancia-támogatás is a nyelvben, ami nem “broken”, méghozzá a generikus delegate-ek:

private static string GetString()
{
    return "http://otperc.net";
}

static void Main(string[] args)
{
    Func<object> functionThatReturnsObject;
    functionThatReturnsObject = GetString;
    object o = functionThatReturnsObject();
}

A Func<T> egy T típust visszaadó metódust reprezentál - tehát a Func<object> egy olyat, ami object-et ad vissza. Ez a "metódusreferencia" bátran mutathat olyan metódusra, ami string-et ad vissza. Mi baj lehet? Semmi. A hívó ugy is object-et vár, nem érheti meglepetés. Bármi olyanra mutathatunk, ami specializáltabb, mint mi (az object-es példa esetében praktikusan bármire). Visszafele viszont nem megy a dolog, ha a hívó string-et vár, nem adhatunk object-et.

A generikus delegate-ek a castolása visszaadott típusokra nézve kovariáns.

Nézzük a másik esetet, mikor a típusparaméter nem a visszaadott típust, hanem a paraméter típusát mondja meg. Az Action<T> delegate egy visszatérési érték nélküli, egy T típusú paramétert váró metódust reprezentál. Ha egy metódus mondjuk object-et vár, bátran elérhetjük egy olyan Action-ön keresztül, ami string-et (vagy bármi már specializáltabbat) vár, mert az "alatta lévő", object-et váró metódus simán meg fogja enni:

private static void ConsumeObject(object obj)
{
    Console.WriteLine(obj);
}

static void Main(string[] args)
{
    Action<string> methodThatConsumesString = ConsumeObject;
    methodThatConsumesString("http://otperc.net");
}

A generikus delegate-ek castolása a paraméterek típusára nézve kontravariáns - a típusbiztosság pedig mindkét esetben compile-time ellenőrzött.

Variancia a C# 4.0-ban

Eddig láttunk példát a kontra- és kovarianciára is, annak jó és kevésbé jó implementációjára tömböknél, delegate-eknél. Egy valamit nem láttunk általános generikus típusoknál, pl. egy List<T>-nél a variancia használatára. Azért nem láttunk, mert nincs. C# 3.0-ig nem volt lehetőségünk mondjuk egy List<string>-et List<object>-té castolni - C# 4.0-tól majd lesz. Bizonyos esetekben.

Nem meglepő, hogy ezt a fajta támogatást a nyelvbe "nem-broken" módon igyekeznek behozni - a klasszikus kecskés-káposztás felállásban, miszerint használhassunk kontra- vagy kovariáns hozzárendeléseket ott, ahol azok működőképesek és hasznosak, de maradjon meg a fordításidejű típusbiztosság-ellenőrzés is. Hogy tudjuk szétszeparálni az működő eseteket a nem-működőktől? Nézzük meg újra a legelső példakódunkat:

GetDataMethod(objectArray); // ez a metódus lefut
SetDataMethod(objectArray); // ez ArrayTypeMismatchException-t dob

Miért tud futni az első, és miért nem a második? Mi a különbség?
Mondjuk első közelítésben az "adatáramlás iránya". Az elsőben csak iterálunk, "kiveszünk" adatokat, míg a másodikban "berakni" próbálunk.

A delegate-es példánál is, a visszatérési érték típusa ("kimenő irány") kovariáns - ami X típust ad vissza, az visszaadhat X-et, vagy bármi specializáltabbat. A másik irányba meg fordítva.

A C# 4.0-ba két új kulcsszó került be a variancia támogatására: az in és az out varianciamódosítót (jó, egyikük kulcsszó-pályafutásának sem ez a kezdete, de ez most egy új szerepkör). Ezzel a két kulcsszóval generikus interface-ek típusparamétereit jelölhetjük meg, mint "bemeneti" (kontravariáns) vagy "kimeneti" (kovariáns) paraméter.

Egy példa talán érthetőbbé teszi a dolgot: vegyük például az IEnumerable<T> generikus interfészt. Egy ilyen osztályból T típusú elemek "jönnek ki" - tehát T-nél "nagyobb", általánosabb elemeket is kivehetünk. Egy IEnumerable<string> vígan tud IEnumerable<object>-ként is viselkedni. Bajt nem csinálhatunk, mert "befele" nem megy adat, nem fordulhat elő, hogy egy, magát IEnumerable<object>-nek mutató IEnumerable<string>-be egy int típusú adatot rakunk, merthogy az object megbírja azt is.

A .NET Framework 4.0-ban ennek az interfésznek így néz ki a definíciója:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

Azzal, hogy ki lett rakva az out, a T paraméter kovariánssá vált:

// C# 4.0 kód:
IEnumerable<string> enumerableOfString =
    new string[] { "http://", "otperc", ".net" };
IEnumerable<object> enumerableOfObject = enumerableOfString;

Annak pedig, hogy az out-ot ki lehetett rakni, egy előfeltétele volt: a T csak a "kimeneti oldalon" szerepelt az interfész definíciójában.
Most nézzünk meg egy másik interfészt:

public interface IComparer<in T>
{
    int Compare(T x, T y);
}

Itt most az "in" módosítót használták, a T paramétert kontravariánssá téve - talán most már nem túl meglepő a felfedezés, hogy a T csak bemenő paraméterként használt. T helyére bepasszol T, vagy bámi nála specializáltabb: egy comparer, ami össze tud hasonlítani két object-et, két stringet is össze tud.

Természetesen az in és az out típusparaméterek használata nem csak a framework fejlesztők kiváltsága, mi is használhatjuk őket a saját interfészeinkben.

Osztályokon nem, azt egy "Invalid variance modifier. Only interface and delegate type parameters can be specified as variant." üzenettel jutalmazza a fordító. A variancia ugyanis műveleteken értelmezett, a műveleteket pedig leginkább a delegate-ek és az interfészek írják le. Az List<T> típusparaméterére nem rakhattak ilyen módosítót, mert a T ki- és bemenő oldalon is használt. Viszont az List<T> egyben IEnumerable<T> is (illetve IEnumerable<out T>), úgyhogy ha "azt az arcát mutatja", akkor használhatjuk úgy.

Szintén ellenőrzi a fordító, hogy ha in vagy out varianciamódosítót használsz, akkor a típusparamétered tényleg csak a megfelelő helyen bukkanjon fel, out paramétert bemenőként használva "Invalid variance: The type parameter 'T' must be contravariantly valid on '<metódusnév>'. 'T' is covariant." üzenetet kapunk, in-t kimenőként használva pedig "Invalid variance: The type parameter 'T' must be covariantly valid on '<metódusnév>'. 'T' is contravariant."-et.

A teljes és kendőzetlen igazság

Be kell vallanom: a fenti postból sok minden nem igaz. Illetve igaz, csak nem úgy. Néhol egyszerűsítésekkel éltem, pl. a “kisebbség-nagyobbság”, “hozzárendelhetőség” és a “leszármazás” fogalmait illene jobban tisztába rakni. Belátható, hogy a kettő nem ugyanaz, mert bár fentebb láttuk, hogy az IEnumerable<object>-nek símán értékül adhatjuk az IEnumerable<string>-et, ez az “egyes számú szabály” értelmében azt is jelenti, hogy az IEnumerable<string> öröklési láncában (bocs, megint pongyola vagyok, interfészről beszélünk: öröklési fájában) valahol szerepelnie kéne az IEnumerable<object>-nek. Egyet biztosíthatok: nem szerepel.

Ha ezek a részletek is a helyükön lennének, akkor korrekt lenne az írás, viszont érthetetlen. A neten rengeteg anyag fellelhető, but I won’t bring it to you – you have to bing it for yourself. :)

2009. december 10., csütörtök

Összes tábla összes adatának törlése SQL Serverben

Fejleszési-tesztelési időben előfordul, hogy szeretnénk "nyomni egy resetet" az adatbázisunkon: a séma marad, de ki szeretnék dobni minden adatot. TRUNCATE-eljünk mindent!

-- disable all constraints
EXEC sp_MSForEachTable "ALTER TABLE ? NOCHECK CONSTRAINT all"

-- delete data in all tables
EXEC sp_MSForEachTable "DELETE FROM ?"

-- enable all constraints
EXEC sp_MSForEachTable "ALTER TABLE ? WITH CHECK CHECK CONSTRAINT all"

A középső sor a tényleges truncate, a kezdő-záró ALTER TABLE-ök csak a kényszerek betartatását kapcsolják ki, majd vissza - így a FOREIGN KEY-ek sem akadályozzák meg az adatok törlését.


az eredeti script és további bölcsességek a témában:
How do you truncate all tables in a database using TSQL? @ StackOverflow

az sp_MSforeachtable tárolt eljárás további hasznos felhasználási lehetőségei:
8 Common Uses of the undocumented Stored Procedure sp_MSforeachtable @ SQLServerCurry

2009. december 7., hétfő

Forráskód másolása HTML-ként Visual Studio-ból

Ha valamilyen technikai irományt (dokumentációt, blogbejegyzést, stb...) készítünk, gyakran lehet szükség arra, hogy abba forráskódot illesszünk be. Ez még nem agysebészet, Visual Studióban kódrészlet kijelöl, CTRL+C, CTRL+V, és kész, átmegy a szöveg.
Egy dolog viszont nem megy át: az a gyönyörű syntax highlight (na jó, Word-be átmegy). Pedig a Studio kódkiemelős funkciója remek, jó lenne azt úgy például HTML-ként kiemelni.

Pont ezt tudja a CopySourceAsHtml nevű Visual Studio add-on, amit ingyenesen le lehet tölteni a http://copysourceashtml.codeplex.com/-ról.
A 3.0-ás verzióban már egy .msi installert kapunk, next-next-finish telepítéssel. Utána egy egy VS újraindítás, és a File, Edit menükben, illetve a kijelölt szöveg context menüjében egy új elem bukkan fel: a Save/Copy as HTML.
Ha már vannak saját CSS stílusaink, azok használatára is beidomíthatjuk.

2009. december 5., szombat

Nullérték-vizsgálat egyszerűen

Eredeti (Java-s) ötlet itt: http://cesjava.freeblog.hu/archives/2009/10/13/a_NUllPointerException_transzparens_kezelese/.

Saját továbbgondolás (C#):

public static class NullReferenceCheckExtensions

{

    public static void CheckIfNull<T>(this T instance)

        where T : class

    {

        if (instance == null)

        {

            string argumentInfo = TryGetArgumentInfo();

 

            if (argumentInfo == null)

                argumentInfo = string.Format("An instance of type '{0}'.", typeof(T).FullName);

 

            throw new ArgumentNullException(argumentInfo);

        }

    }

 

    private static string TryGetArgumentInfo()

    {

        try

        {

            var st = new System.Diagnostics.StackTrace(true);

            var callerFrame = st.GetFrame(2);

            var fileName = callerFrame.GetFileName();

            var fullSource = System.IO.File.ReadAllLines(fileName);

            var info = fullSource[callerFrame.GetFileLineNumber() - 1].Trim();

            return info;

        }

        catch

        {

            return null;

        }

    }

}

Használat:

public void MyMethod(string name, Uri uri)

{

    name.CheckIfNull();

    uri.CheckIfNull();

}

2009. december 4., péntek

Osszunk nullával!

double one = 1;

double zero = 0;

try

{

    var x = one / zero;

}

catch (DivideByZeroException)

{

    Console.WriteLine("Csak hiszed...");

    Console.WriteLine("http://www.blackwasp.co.uk/CSharpInfinity.aspx");

}

2009. december 3., csütörtök

WCF exception faultá alakítása háziassszonyoknak

Adott a szituáció, hogy van egy üzleti logia / üzleti folyamat rétegünk, amiből szeretnénk néhány dolgot publikálni a külvilág felé - ehhez a WCF a választott technológiánk. Néhány dolgot nem publikálunk, azokat csak a service réteget assemblyként referenciáló kódok érik el (ők is a sajátjaink, a rendszer többi komponense).
A WCF remek választás, hisz pont erről szól: úgy implementálhatod a service rétegedet, hogy arra koncentrálsz, amit csinálni akarsz - azzal meg nem kell törődnöd, hogy hogy lesz ez a szolgáltatás távolról elérhető. Nem kell a kommunikációval foglalkozó kódot írnod, csak felpattintasz néhány attribútumot (, átfusz néhány ezer sor XML-t), és kész. Eddig hurrá.

A feketeleves ott kezdődik, hogy az üzleti folyamatok végrehajtása nem mindig sikeres (akár azért, mert nem álltunk a helyzet magaslatán, mikor implementáltunk, akár azért, mert valami külső körülmény - pl. egy elhalálozott adatbázisszerver - miatt nem tudjuk teljesíteni kliensünk kérését).
Vegyük pl. a jó öreg számológép szolgáltatást - jelen esetben a megvalósításunk, a FawltyCalculator kicsit bugos. A metódusunk hívása egy kövér DivideByZeroException-t fog dobni.

[ServiceContract]

interface ICalculator

{

    [OperationContract]

    int Divide(int dividend, int divisor);

}

 

public class FawltyCalculator : ICalculator

{

    public int Divide(int dividend, int divisor)

    {

        int result;

        result = dividend / (divisor * 0); //buggy

        return result;

    }

}

A dolgok ilyentén való félremenésére a kliensoldalon is fel kell készülni. A WCF megteszi nekünk azt a szivességet, hogy a szolgáltatások futása közben dobott, kezeletlen kivételeket szépen megeszi, és a szervíz hívásra küldött válaszban jelzi, hogy baj van (volt).
A túloldalon (a kliensnél) ez egy FaultException képében fog materializálódni. Ott tehát nem egy DivideByZeroException elkapására kell felkészülni, hanem egy FaultException-ére, amivel egy baj lesz: semmit nem fogunk tudni arról, hogy mi is ment félre, kifutottunk a memóriából, nullával osztottunk, vagy kihalt alólunk valami egyéb szoftverkomponens. Megtehetjük ugyan, hogy arra instruáljuk a WCF-et, hogy adja vissza a kliensoldalnak a részletes exception-t, ami development-time hasznos feature, de produkciós környezetben nem túl szerencsés (egyrészt nem túl user friendly megoldás, másrész túl sok mindent köthetünk így a klienseink orrára, amit esetleg nem szeretnénk).

A megoldás: dobjunk magunk is FaultException-t, már a service oldali kódban! Annak megadhatunk FaultReason-t, FaultCode-ot, az szépen át fog menni a kliens oldalra:

    public int Divide(int dividend, int divisor)

    {

        int result;

        try

        {

            result = dividend / (divisor * 0); //buggy

        }

        catch (DivideByZeroException)

        {

            throw new FaultException(

                new FaultReason("A nullával való osztás nem menő."),

                new FaultCode("DIVIDE_BY_ZERO")

                );

        }

        return result;

    }

Ez eddig remek. Mi vele a gond? Az, hogy a WCF azt ígérte nekünk, hogy úgy kódolhatunk szolgáltatást, hogy közben nem kell ilyesmivel törődni. Márpedig ha a Divide() metódus nem egy WCF-en át elérhető szolgáltatás lenne, hanem csak úgy meghívnánk kódból, akkor még úgy is nehezen indokolható a fenti try-catch blokk, ha nem véletlenül vagyunk mi a FawltyCalculator dedikált fejlesztői.
Mi történik itt? Ez az absztrakció szivárog.
Sőt, ez inkább már folyik.

Az lenne a jó, ha nem kéne FaultException-t dobnunk, de mégis, akkor, és csakis akkor, ha a metódunkat WCF-en keresztül, távolról hívják, valahogy mégis úgy legyen minden, minthacsak azt dobtunk volna.
Szerencsére van megoldás, úgy hívják, hogy IErrorHandler (a System.ServiceModel.Dispatcher névtér alatt lakik). Nem túl meglepő módon ez egy interface, ami két metódus megvalósítását írja elő: a HandleError()-ban a szervízoldalon dobott, kezeletlen kivétellel kezdhetünk valamit (logging, alkalmazás meghalasztása, stb.), a ProvideFault() pedig pont az, ami nekünk kell: az exception ismeretében megkonstruálhatjuk a kliensünknek adandó választ. Ezt akár bit (na jó: XML element) szinten is kontrollálhatjuk, de ha lustábbak vagyunk, rábízhatjuk a Frameworkre is a legyártását:

public void ProvideFault(Exception error, System.ServiceModel.Channels.MessageVersion version, ref Message fault)

{

    FaultException faultException = null;

 

    if (error is ApplicationException)

    {

        faultException = new FaultException(

            new FaultReason(string.Format("Alkalmazáshiba történt ('{0}').", error.Message)),

            new FaultCode("APPLICATION_ERROR")

            );

    }

    else if (error is System.Data.Linq.ChangeConflictException)

    {

        faultException = new FaultException(

            new FaultReason(string.Format("Több felhasználó próbálta párhuzamosan módosítani ugyanazt az adatot.")),

            new FaultCode("DATABASE_CONCURRENCY_ERROR")

        );

    }

 

    // Still unhandled - provide some default...

    if (faultException == null)

    {

        faultException = new FaultException(

            "Kezeletlen kivétel történt.",

            new FaultCode("UNHANDLED_ERROR"));

    }

 

    var messageFault = faultException.CreateMessageFault();

 

    // ref Message fault

    fault = Message.CreateMessage(

        version,

        messageFault,

        "http://www.w3.org/2005/08/addressing/soap/fault"

        );

}


[Felhívnám a figyelmet a "http://www.w3.org/2005/08/addressing/soap/fault" sorra, azt magában fél nap volt kififikázni.]

Ezek után már csak rá kell vennünk a WCF-et, hogy használja is az errorhandlerünket.
Ehhez készítenünk kell egy osztályt, ami implementálja az IServiceBehavior interfészt. Ennek az osztálynak a ApplyDispatchBehavior() metódusában fogjuk az ErrorHandlerünket befűzni a WCF folyamatába. Ezután három út van: a) programozottan bepéldányosítjuk a service behaviorunkhat, és megetetjük a service host objektummal, b) az IServiceBehavior megvalósításon kívül leszármazunk az Attribute-ból is, és attribútumként felpattintjuk a szervizünkre c) az IServiceBehavior megvalósításon kívül leszármazunk a BehaviorExtensionElement-ből, és az app.configban konfiguráljuk az error handlingot. (Ez mekkora!)
Én a b)-t választottam:

public sealed class ErrorBehaviorAttribute : Attribute, IServiceBehavior

{

    private Type _typeErrorHandler;

 

    public ErrorBehaviorAttribute(Type typeErrorHandler)

    {

        _typeErrorHandler = typeErrorHandler;

    }

 

 

    public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase) { }

    public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters) { }

 

    private IErrorHandler CreateTypeHandler()

    {

        var typeErrorHandler = (IErrorHandler)Activator.CreateInstance(_typeErrorHandler);

        return typeErrorHandler;

    }

 

    public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)

    {

        IErrorHandler typeErrorHandler = this.CreateTypeHandler();

        foreach (var channelDispatcher in serviceHostBase.ChannelDispatchers)

        {

            (channelDispatcher as ChannelDispatcher).ErrorHandlers.Add(typeErrorHandler);

        }

    }

}


Ennyi. Megy. Persze lehet még tovább cifrázni, pl. hosszú távon a ProvideError() jó eséllyel hízik túl minden vállalható méreten, de ez már egy másik sztori.
Mára ennyit!