Tag Archives: C#

Singleton unit tesztelése, avagy .NET-ben miért nem 12 hónapból áll az év?

Íme mai vizsgálódásunk tárgya:

public class Calendar
{
  private static Calendar _instance;

  public static Calendar Instance { ... }

  private Calendar() { }

  public string[] GetMonthNames()
  {
    string[] names = DateTimeFormatInfo.CurrentInfo.MonthNames;
    return Sorter.Alphabetically(names);
  }

  // És még sokan mások...
}

Két dolgot mindenképp érdemes ezen az osztályon megfigyelni: van egy GetMonthNames metódusa és követi a Singleton tervezési mintát. A kettő nyilván összefügg, a metódust Calendar.Instance.GetMonthNames() formában lehet meghívni.

Mivel ez egy nagyobb, korosabb kódbázis része, mielőtt nagyobb változtatásokba fogunk, írjunk unit teszteket a GetMonthNames függvényhez. És itt rögtön belefutunk abba a problémába, hogy nem tudjuk izolálni a metódust, bele van drótozva, hogy függ a DateTimeFormatInfo és a Sorter osztályoktól. Az előbbi a .NET Framework része, az utóbbit viszont én találtam ki, ugyanebben a projektben van:

public static class Sorter
{
  public static string[] Alphabetically(string[] values)
  {
    return values.OrderBy(v => v).ToArray();
  }
}

Alakítsuk át, de úgy, hogy a Calendar osztályban lévő 123 másik metódust, és az őket hívó kódot ne kelljen módosítani.

A függőséget írjuk le egy interfésszel, mert azt könnyű lesz mockolni:

public interface ISorter
{
  string[] Alphabetically(string[] values);
}

Implementáljuk úgy, hogy visszavezetjük a már meglévő, static megvalósításra:

public class SorterWrapper : ISorter
{
  public string[] Alphabetically(string[] values) => Sorter.Alphabetically(values);
}

Másként fogalmazva eddig annyit csináltunk, hogy a static implementációt becsomagoltuk egy példányosítható implementációba, amit egy interfésszel is le tudunk írni.

Mivel az eredeti Calendar osztály nyilvános interfészéhez nem akarunk hozzányúlni, vezessünk be újabb osztályt, amit én fantáziadúsan MonthManager-nek neveztem el:

public class MonthManager
{
  private ISorter _sorter;

  public MonthManager(ISorter sorter)
  {
    this._sorter = sorter;
  }

  public string[] GetMonthNames()
  {
    string[] names = DateTimeFormatInfo.CurrentInfo.MonthNames;
    return this._sorter.Alphabetically(names);
  }
}

Ez már egy kiválóan tesztelhető osztály, aminek a függőségét a konstruktoron keresztül meg lehet adni. (Az egyszerűség kedvéért a DateTimeFormatInfo függőséggel itt szándékosan nem foglalkozom.)

Módosítani kell természetesen az eredeti Calendar osztályt is, de szerencsére csak a belső implementációját, a publikus interfésze változatlan marad:

public class Calendar
{
  // Singleton...

  private MonthManager _monthManager;

  private Calendar()
  {
    ISorter sorter = new SorterWrapper();
    this._monthManager = new MonthManager(sorter);  
  }

  public string[] GetMonthNames()
  {
    return this._monthManager.GetMonthNames();
  }
}

Ehhez már könnyen írhatunk teszteket:

public class IdentitySorter : ISorter
{
  public string[] Alphabetically(string[] values) => values;
}

[TestClass]
public class CalendarTests
{
  [TestMethod]
  public void ShouldReturnTwelveMonths()
  {
    MonthManager mgr = new MonthManager(new IdentitySorter());
    string[] months = mgr.GetMonthNames();
    Assert.AreEqual(12, months.Length);
  }
}

Igazán kár, hogy ez a teszt hibát fog jelezni, méghozzá mindig! Látod a hibát?

Nem? Mert nincs is! Ez feature 🙂

A .NET Frameworkben lévő System.Globalization.Calendar osztályt úgy tervezték, hogy mindenféle naptárt le tudjon írni, van is 11 származtatott osztálya, köztük például a GregorianCalendar. Természetesen az egyes naptárak máshogy értelmezhetik a hónap fogalmát és ennek megfelelően más lehet a számuk is. Ennek a támogatását pedig úgy sikerült megoldani, hogy a DateTimeFormatInfo osztály MonthNames tulajdonsága CurrentCulture-tól függetlenül mindig 13 (!) elemű tömböt ad vissza, ami a mi naptárunk esetén a 12 hónap nevét jelenti, a 13. elem pedig egy üres string. Mindezért persze nem a .NET a felelős, így van ez a Win32 API-ban is (lásd LOCALE_SMONTHNAME13 konstans) már jó rég óta.

Színvonalas (?) állásajánlatok

Időnként kapok leveleket állásajánlatokkal, de ennyire “színvonalas” még nem jutott el hozzám.

Tárgy: C# és Java pozíciók

Már ebből látszik, hogy ez mennyire rám van szabva. Én és a Java…

Kedves régi/új Jelöltünk!

Az adatbázisunkban találtam meg az önéletrajzát, …

Én az új jelöltek közé tartozhatok, mert még soha nem kaptam levelet Önöktől. Én biztosan nem kértem, hogy az életrajzom kerüljön be az adatbázisukba, bár végülis nyilvánosan kint van a neten, bárki letöltheti.

… mely alapján a következő lehetőségek közül Java vagy C# területen ajánlom Önnek aktuális pozícióinkat:

Azért arra kíváncsi lennék, hogy a Javát hogy sikerült hozzám párosítani. Ezek után a levélben jön egy lista, például ilyen tételekkel:

C#:

Junior C#.Net or VB.NET developer

Na kösz.

Kérem, amennyiben érdekli valamely fenti lehetőség, és úgy gondolja, meg is felel az elvárásoknak, úgy válaszoljon az alábbi kérdésekre, és küldjön frissített magyar és angol szakmai önéletrajzot word formátumban, ha lehetséges fényképpel:

Hány év C# tapasztalta van?

Milyen szinten beszél angolul? Használja-e naponta?

Van-e német nyelvtudása?

Nem értek a HR szakmához, de nem erre való az életrajz, vagy az a bizonyos adatbázis?

Mennyi a havi nettó és bruttó  fizetési igénye (bécsi lehetőségre: számlás óradíja)?

Látom, ez az első szűrő.

Ezek után jön 6 senior Java fejlesztői állás, gondolom megint az én CV-m alapján.

Mostani megkeresésünk során kulcsszavas keresés kapcsán találtuk meg önéletrajzát, elnézést amennyiben fő profiljába mégsem illenének bele.

Azért azokra a kulcsszavakra kíváncsi lennék.

Az egész levelet a melléket koronázza meg: sikerült ugyanis elküldeni 548 db fejlesztő nevét, telefonszámát, e-mail címét és érdeklődési körét egy mellékelt Excel fájlban. A listában szerepelek én is, és találtam még ismerősöket, gondolom ez volt a körlevél alapja.

Tisztában vagyok vele, hogy az álláspiac manapság inkább húspiac, de ennek tényleg így kell történnie?

C# 5.0 caller info attributes és INotifyPropertyChanged

A C# 5.0-ban lesz néhány új nyelvi elem, melyek közül az egyik a “caller info attributes. Egyszerűen fogalmazva arról van szó, hogy ha egy metódus bemeneti paramétereit ellátjuk néhány attribútummal, akkor azok a paraméterek futási időben meg fogják kapni a hívó függvény nevét, valamint annak a fájlnak a teljes elérési útját és azon belül a sor számát, ahol a hívó található. Az attribútumok:

[CallerMemberName]

[CallerFilePath]

[CallerLineNumber]

A legtöbb felhasználási példa arról szól, hogy ezek az attribútumok milyen kiválóan használhatók naplózásra, de használhatjuk őket például az INotifyPropertyChanged interfész implementálásakor is:

public class Widget : INotifyPropertyChanged
{
    private string statusText;
    public string StatusText
    {
        get { return statusText; }
        set { statusText = value; NotifyPropertyChanged(); }
    }

    public void NotifyPropertyChanged([CallerMemberName] string property = null)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
 
   public event PropertyChangedEventHandler PropertyChanged;
}

A példát a C# FAQ blogból kölcsönöztem, ahol további linkek találhatóak a C# 5.0-val és a Visual Studio 11 Bétával kapcsolatban.

 

Technorati-címkék: ,

Strong name mások szerelvényéhez

A minap az egyik projektünkben Excel 2003 kimenetet kellett gyártani, amihez az ExcelLibrary-t használtam. Ez egy olyan szabadon felhasználó osztálykönyvtár, amit épp DLL formában könnyű letölteni. Mikor azonban a saját forráskódunkba akartam beépíteni, az alábbi hibaüzenet fogadott:

Error: Referenced assembly ExcelLibrary does not have a strong name.

Puff neki, még a végén kénytelen leszek letölteni a forráskódot, valahogy lefordítani és úgy aláírni?

Hát nem, van annál gyorsabb megoldás is, csak kell hozzá egy Visual Studio Command Prompt:

  ildasm /all /out=ExcelLibrary.il ExcelLibrary.dll
  ilasm /dll /key=my.snk ExcelLibrary.il

És már fordult is Mosolygó arc

 

Technorati-címkék: ,,,,

Dynamic DataRow

Azt hiszem nem én vagyok az egyetlen, akit a C# 4-ben bevezetett dynamic kulcsszó első hallásra teljesen hidegen hagyott. Meg másodikra és harmadikra is. Hiszen száz évben egyszer van (vagy egyszer sincs) szükség arra, hogy .NET-ből COM-ot hívjak, Office-t automatizáljak, Pythonnal és Ruby-val meg aztán végképp nem játszom – márpedig az összes (new Calc-nál bonyolultabb) példa ezekről szól. De azért csak találtam olyan esetet, ahol kifejezetten bevált a dynamic. Nem azért, mert nem lehetne máshogy, hanem azért, mert így rövidebb lesz a kód.

Ez az ominózus eset pedig a DataRow objektumból történő adatolvasás. Az sem túl szép, hogy row["oszlopnév"], de onnan kezdve, hogy nem objectre, hanem stringre, intre vagy boolra van szükségünk és kasztolgatni kell, kifejezetten terjedelmessé és barátságtalanná tud válni a kód. Ezen segíthet az alábbi osztály:

public class DynamicDataRow : DynamicObject
{
    private readonly DataRow row;

    public DynamicDataRow( DataRow row )
    {
        this.row = row;
    }

    public override bool TryGetMember( GetMemberBinder binder, 
out object result ) { object rawValue = this.row[ binder.Name ]; result = rawValue is DBNull ? null : rawValue; return true; } public override bool TrySetMember( SetMemberBinder binder,
object value ) { this.row[ binder.Name ] = value; return true; } }

Ha van egy ilyen osztályunk, akkor sokkal egyszerűbben férhetünk hozzá típusosan egy DataRow objektum tartalmához:

DynamicDataRow r = new DynamicDataRow( row );
bool b = r.BoolOszlop;

Amire érdemes figyelni:

  • A TryGetMemberbe szándékosan került egy olyan rész, ami kezeli azt az esetet, amikor az adatbázisból NULL érték jön vissza például string helyett. Ilyenkor ugyanis a dynamic DBNull típusra alakítaná és máris nem tudnánk olyat leírni, hogy String.IsNullOrEmpty(r.Oszlop).
  • A másik, hogy a Nullable típusok meg tudják ám zavarni a dynamicot. Ha ő azt látja, hogy egy oszlop egész számot tartalmaz, akkor ő azt intre fogja alakítani. Azt már ő nem fogja tudni, hogy az az oszlop néha lehet NULL is, amit egyébként egészen jól lehetne kezelni int? típusként is. Ha a kódodban később van egy null vizsgálat, ami int?-re jól működik, egyáltalán nem biztos, hogy dynamicra is jól fog működni, például mert a dynamicban lévő intnek nincs HasValue tulajdonsága.
Technorati-címkék: ,,