2009. február 17., kedd

Konstuktőrök versenye: kivételes eredmények

Kivételt dobni jó! (kivéve persze amikor nem az)
Kivételt dobhatunk, bárhol, bármikor - legfejlebb furcsa következményei lesznek. Például ha egy statikus konstruktorból szökik ki egy kezeletlen kivétel, azt nem sok esélyünk lesz elkapni, tekintve, hogy egy osztály statikus konstuktorát nem mi hívjuk, hanem a CLR a típushoz való első hozzáférés előtt valamikor - ő pedig ezt úgy fogja értelmezni, hogy ennek a típusnak az inicializálása nem sikerült túl jól, úgyhogy a jövőben leszünk szívesek tartózkodni a használatától (legalábbis a bűnös AppDomainben).

Kivételt persze dobhatunk nem csak statikus, de "hagyományos" (példány) konstruktorból is. Itt már jobb esélyekkel indulunk, ha ezeket kezelni is szeretnénk, de furcsaságokkal találkozhatunk itt is. Vegyük pl. az alábbi osztályt:

class MyType
{
    public string Name { get; private set; }

    public MyType(string name)
    {
        // Ezzel most egyelőre ne törődjünk:
        // InstanceLibrary<MyType>.RegisterInstance(name, this);
        Name = name;

        if (name.Length > 5)
            throw new ArgumentException("The name is too long.");
    }
}
Eddig semmi extra, egy példány, egy név, a nevet megkapjuk konstruktor paraméterben, illetve egy extra ellenőrzés: ha név hosszabb, mint öt karakter, akkor arra azt mondjuk, hogy na ezt ne, ez már tényleg sok.
Van még egy kikommentezett sorunk, azzal egyelőre ne törődjünk, majd mindjárt meglátjuk mire lesz jó. Vigyázó szemeink vessük inkább az alábbi kódra:
MyType i1 = null, i2 = null, i3 = null;

try
{
    i1 = new MyType("Jenő");
    i2 = new MyType("Benő");
    i3 = new MyType("Szigfrid");
}
catch(Exception ex)
{
    Console.WriteLine("An exception has been thrown: {0}", ex.Message);
    Console.WriteLine("i1={0}", i1 != null ? i1.Name : "[null]");
    Console.WriteLine("i2={0}", i2 != null ? i2.Name : "[null]");
    Console.WriteLine("i3={0}", i3 != null ? i3.Name : "[null]");
}
Mi lesz ebből? Az első két instance szépen létrejön, Szigfrid már kiveri a biztosítékot, de szépen lekezeljük a kivételt, ő (i3) null lesz, az objektum már létre sem jön.
Legalábbis azt gondolhatnók (C++ múlttal rendelkezők akár a nagyesküt is leteszik) - mi egyelőre inkább maradjunk abban, hogy i3 értéke null lesz.

Most nézzük meg a kikommentezett rész. Vegyük az alábbi remek kis példány-tárolót, és vegyük ki a kommentet a MyType konstruktorából.

static class InstanceLibrary<T>
{
    private static Dictionary<string, T> _instacesByName = new Dictionary<string, T>();
    
    public static void RegisterInstance(string name, T instance)
    {
        _instacesByName.Add(name, instance);
    }

    public static IEnumerable<T> AllInstances
    {
        get { return _instacesByName.Values; }
    }
}
Majd a fenti try-catch-es konstruálási folyamat után menjünk végig az összes létrehozott példányon:
foreach (var instance in InstanceLibrary<MyType>.AllInstances)
{
    Console.WriteLine(instance.Name);
}
A már hagyományőrző találós kérdés: hány példány lesz?
Hát persze, hogy három. Sőt, a legjobb, hogy az elvileg nem létező MyType példányunk, Szigfrid, rezzenéstelen arccal közli velünk kivételesen hosszú nevét.

A CLR tehát a konstuktorból dobott kivétel ellenére létrehozott egy példányt, bár a new operátor már nem adott vissza nekünk referenciát erre a félig-meddig megkonstruált instancera - bölcs döntés, hisz az valószínűleg valamilyen inkonzisztens állapotban van, nem lenne szerencsés használni.
De ettől még létezik, bár referencia nincs rá - ideális zsákmányállat a GC számára. Szépen ki is fogja dobni, meg finalizálni, meg amit szokott csinálni az ilyen alakokkal. Ha finalizert is csinálunk az osztályunkhoz, IDisposable-ek vagyunk, nem árt erre is felkészülni (például úgy, hogy ha a konstruktorban tudjuk, hogy baj lesz, már ott hívunk egy GC.SuppressFinalize(this)-t).

Tanulság?
Szintén hagyományőrző jelleggel: nincs. A lehetőségeink határtalanok, de figyeljünk oda mit csinálunk, mert a szoftverfejlesztés - managed vagy nem managed - veszélyes üzem: könnyen lábon lőhetjük magunkat. A lehetőségeink ugyanis e téren is határtalanok.

2009. február 8., vasárnap

Google Translate Based ResourceProvider

Annyira szerelembe estem a Google Translate szolgáltatásával, illetve annak .NET API-jával, hogy készítettem egy erre épülő resource provider implementációt (elérhető a http://www.codeplex.com/GTBResourceProvider címen).
Ez annyit csinál, hogy először a "hagyományos", .resx fájlokra támaszkodó providerrel beolvassa a lokalizált erőforrásokat, és ha az adott nyelven nem sikerült elérnie azt, akkor automatikusan lefordítja őket.

Ha a webalkalmazásunk már egyébként is a resource fájlokra támaszkodott a sztringliterálok többnyelvűsítéséhez (azaz nem a kódunkba varrtuk be a "Ez egy üzenet." jellegű dolgokat), akkor igazán egyszerű a használatba vétele:

  • Referenciáljuk be az OtPercDotNet.GoogleTranslateBasedResourceProvider assembly-t
  • A Web.config /configuration/system.web/globalization ágában állítsuk be resourceProviderFactoryType-ot:
    <globalization resourceProviderFactoryType="OtPercDotNet.GoogleTranslateBasedResourceProvider.GoogleTranslateBasedResourceProviderFactory" />
Ennyi.

2009. február 6., péntek

Ha szennyezett a lemez arca

A Google API for .NET project célja, hogy egy managed felületet adjon a Google különböző kereső (web, kép, video, könyv, stb) és fordító szolgáltatásai fölé. Nehezen lehet elképzelni pl. ennél egyszerűbb fordító API-t:

class Program
{
    static void Main(string[] args)
    {
        string sourceText = "Sense/Net 6.0 is an enterprise grade Open Source application suite for building integrated Enterprise Content Management (ECM, ECMS) and Enterprise Portal (EPS) solutions running on the .NET and later the Mono platform. Sense/Net 6.0 is an Open Source alternative of Microsoft SharePoint. Modules include Enterprise Portal, Enterprise Content Management, Enterprise Infrastructure, and Applicaton Framework.";
        string translatedText = Translator.Translate(sourceText, Language.English, Language.German);
    }
}

A fordító szolgáltatás egyébként már támogatja a magyar nyelvet is, de az API még nem tud róla.
Valószínű hamarosan javítják (egy enum értéket kell hozzáadni egy tömbhöz, nem nagy meló), de egy kipróbálás erejéig hackelhetünk reflection-nel:

List<Language> translatableLanguages = new List<Language>(Google.API.Translate.LanguageUtility.TranslatableCollection);
if (!translatableLanguages.Contains(Language.Hungarian))
    translatableLanguages.Add(Language.Hungarian);
var translatableLanguagesField = typeof(LanguageUtility).GetField("s_TranslatableList", BindingFlags.NonPublic | BindingFlags.Static);
translatableLanguagesField.SetValue(null, translatableLanguages);
"Sense / Net 6,0 nagyvállalati minőségű nyílt forráskódú alkalmazás lakosztály épület integrált Enterprise Content Management (ECM, ECMS) és a Vállalkozásfejlesztési Portál (EPS) megoldások fut a. NET és később a Mono platformon. Sense / Net 6,0 egy nyílt forráskódú alternatívája a Microsoft SharePoint. Modulokat tartalmazza Enterprise Portal, Enterprise Content Management, Enterprise Infrastructure, és alkalmazásának keret."

A gépi fordítás még messze van a tökéletestől, de azért nem rossz - legalábbis láttunk már sokkal rosszabbat is.

2009. február 2., hétfő

Fókabél vs. Főkábel

Az T-SQL-ben a ' karakterek között adhatunk meg sztringliterálokat (pl: 'Ez egy sztring.'). Ez így persze pongyola, mert az SQL-ben nincs sztring, hanem varchar van (non-unicode), meg nvarchar (unicode).
Itt mindjárt jelentkezik is a pongyola fogalmazás átka: na most akkor melyik?

A válasz: az a'la nature ' karakterek közötti literálok varchar típusúak lesznek, ha nvarchar (unicode) literálra van szükségünk, akkor egy N karakterrel kell prefixelnünk (pl: N'skjønner forstår vær').
Miért fontos ez? Adjuk ki a következő utasítást:

SELECT N'Jóízű félárú sütőtök'
Mi lesz az eredmény? Hát az, hogy Jóízű félárú sütőtök (jó kis kifejezés, benne van az összes magyar ékezetes betű).
Most próbáljuk meg N nélkül:
SELECT 'Jóízű félárú sütőtök'
Nos, mi lesz az eredmény? A helyes válasz: fogalmam sincs. Attól függ, hogy az adatbázis, amelyen futtatod, milyen collation-t használ. Ha magyart, akkor helyes lesz az eredmény, ha nem, akkor az adott kódtáblából hiányzó karakterek (minden valószínűség szerint az "ő" meg az "ű") le fognak cserélődni ékezet nélküli párjukra.

Ez akkor tud kellemetlen lenni, ha egy táblánk egy nvarchar típusú mezőjében van valami unicode szöveg, mi pedig egy olyan query-t futtatunk, ami egyezésre, vagy LIKE-kal hasonlóságra vizsgál egy szöveges literálra (pl. egy kézzel írt query az SQL Managerből, vagy egy, az alkalmazásunkból generált query):

SELECT
    Id, TextValue
FROM
    TestTable
WHERE
    TextValue LIKE '%sütőtök%'
Itt, ha nem rakjuk ki az N prefixet, és a adatbázis collation nem magyar, igazából egy LIKE '%sütotök%' fog lefutni, ami nem az, amit mi szeretnénk.

ASP.NET Cache használata !webalkalmazasból

Amennyiben valamilyen cache megoldásra van szükségünk, webalkalmazásainkban bátran nyúlhatunk a HttpContext.Current.Cache-hez, ami egy elég szofisztikált és saját igényeinkre szabható gyorsítótár-megoldás.
De mi van a nem webes alkalmazásokkal? Ez a cache infrastruktúra onnan is használható, bár kicsit furcsa lehet, hogy a System.Web assemblyt be kell referenciálnunk pl. egy konzol alkalmazásba, de ha ez az ára, akkor ez az ára.

Viszont ilyenkor nincs HttpContext.Current, így értelemszerűen nincs .Cache sem. Workaround lehet, ha készítünk egy "kamu" HttpContext példányt, de még jobb, ha belelapozunk az Új Reflector Magazin aktuális számába: azt írja, hogy a HttpContext osztály Current propertyje semmi mást nem csinál, mint visszaadja a HttpRuntime.Cache-t:

public Cache get_Cache()
{
    return HttpRuntime.Cache;
}
Ami pedig egy public sealed osztály public property-je, úgyhogy semmi sem akadályoz benne, hogy mi is megtegyük.
No more "HttpContext hack" nedded.