Jasica.Net

C#, .Net, SQL i nie tylko

Zadbaj o siebie programisto! Jakość powietrza

O zdrowie trzeba dbać. Nie ma na ten temat co dyskutować. W internecie można znaleźć wiele materiałów zachęcających nas do wyboru krzesła, które ochroni nasz kręgosłup lub monitora, który nie będzie przemęczał naszych oczu. Ale jak często zwracamy uwagę na to czym oddychamy?

Jeśli przebywamy w słabo wentylowanym pomieszczeniu z czasem możemy mieć problemy z kreatywnym myśleniem i koncentracją. W takich przypadkach jedyne co może nam chodzić po głowie to powrót do domu. Najczęściej przyczyną takiego problemu jest wysokie stężenie dwutlenku węgla. Świeże powietrze za zawiera około 400ppm(ang. parts per million). Akceptowalna norma w pomieszczeniach biurowych to 600ppm, a 1000ppm jest minimalnym wymogiem higienicznym. Bez odpowiedniego oprzyrządowania liczby te pozostaną tylko pewną normą nie dającą się w praktyce utrzymać. Z pomocą może przyjść jednak mały gadżet. 

 

Voltcraft CO-20 jest niewielkim, eleganckim urządzeniem, zaprojektowanym tak by był łatwy w użyciu. Stan powietrza określa przy pomocy diód świecących w trzech kolorach: zielonym, żółtym i czerwonym. Zielony kolor intuicyjnie oznacza, że powietrze jest w najlepszym porządku, żółte jest już sygnałem, że warto rozważyć przewietrzenie pomieszczenia, a czerwone daje nam sygnał do dłuższej przerwy.

Dla urządzenia została dodatkowo stworzona specjalna aplikacja pozwalająca monitorować na bieżąco stan powietrza. Dane te potrafi zwizualizować w postaci całkiem czytelnego wykresu:

 

 

W przypadku pogorszenia się jakości powietrza zostaniemy przed tym ostrzeżeni w postaci dymka z informacją w zasobniku systemowym. Miernik możemy nabyć za około 99 zł.

Urządzenie posiada też swojego starszego brata:

 

Voltcraft CO-60 ma formę zegaru, na którym zamiast godziny zobaczymy informację o stężeniu dwutlenku węgla. Z wyświetlacza LCD odczytamy także temperaturze i wilgotności powietrza. Podobnie jak poprzedni model posiada trzy diody informujące o jakości powietrza. Niestety, choć efektowny i użyteczny, jest on znacznie drożysz od swojego poprzednika. Urządzenie kosztuje około 459zł.

Niezależnie od tego czy zdecydujemy się na wspomaganie w postaci miernika czy nie powinniśmy zadbać o dopływ świeżego powietrza do naszego miejsca pracy by poprawić jakość swojej pracy.

Nieudokumentowane słowa kluczowe C#

Istnieją 4 słowa kluczowe, o których istnieniu wie niewielu programistów C#: __arglist, __refvalue, __makeref, __reftype. Stanowią one jednak ciekawostkę, o której istnieniu warto mieć pojęcie. Oficjalna dokumentacja(C# Keywords) nie wspomina o nich. Można jednak szybko przekonać się o ich istnieniu, gdyż Visual Studio podświetla je podobnie jak inne słowa kluczowe( domyślnie na niebiesko). Najprawdopodobniej powstały one by dać dostęp do instrukcji CLI takich jak: arglist, refanyval, mkanyref, refanytype, o których mowa w specyfikacji CLI.

Pierwsze słowo kluczowe- __arglist, daje  możliwość, podobnie jak słówko kluczowe params, przekazania wielu parametrów do metody. Niekoniecznie wszystkie muszą być tego samego typu. Poniżej znajduje się przykład użycia:

public static void Main(string[] args)
{
    PrintList(__arglist(1, 2, 3, "Four", 5, "Six"));
}

public static void PrintList(__arglist)
{
    ArgIterator iterator = new ArgIterator(__arglist);
    Console.WriteLine("Count: {0}", iterator.GetRemainingCount());
    Console.WriteLine("Items:");
    object current = null;
    while (iterator.GetRemainingCount() > 0)
    {
        TypedReference item = iterator.GetNextArg();
        Console.WriteLine(TypedReference.ToObject(item));
    }
}

Użycie słowa kluczowego __arglist, w stosunku do params nie jest wcale prostsze, czy też czytelniejsze. Należy zauważyć, że dostęp do argumentów daje dopiero specjalny typ- ArgIterator. Dwie jego najważniejsze metody- GetNextArg i GetRemainingCount, pozwalają pobrać kolejny element z listy i sprawdzić ile jeszcze elementów zostało do przejrzenia. Dodatkowo dostęp do kolejnych elementów listy jest "utrudniony", gdyż zwracane są one jako obiekt typu TypedReference. Jednak po co utrudniać sobie aż tak tworzenie kodu? Odpowiedź nasuwa mi się tylko jedna- ułatwienie współpracy z językami takimi jak C/C++ wspierającymi zmienną liczbę argumentów poprzez operator wielokropka(...). Po więcej na temat samego operatora odsyłam do Wikipedi.

Słówka kluczowe __refvalue oraz __reftype służą do współpracy z obiektami typu TypedReference. Pozwalają na pobranie wartości oraz jej typu. Wypisywanie wartości elementu listy przy pomocy tych słów kluczowych mogłoby wyglądać na przykład tak:

public static void PrintTypeReference(TypedReference item)
{
    Type type = __reftype(item);
    if(type == typeof(int))
    {
        int number = __refvalue(item, int);
        Console.WriteLine("Type: {0}, Number: {1}", type, number);
    }
    else
    {
        string text = __refvalue( item, string);
        Console.WriteLine("Type: {0}, Text: {1}", type, text);
    }
}

Nic nie stoi jednak na przeszkodzie, by skorzystać z statycznych metod struktury TypedReference, które pozwalają pozbyć się "niewygodnych" słów kluczowych. Powyższy kod przetłumaczony wraz z ich użyciem mógłby wyglądać tak:

public static void PrintTypeReference(TypedReference item)
{
    Type type = TypedReference.GetTargetType(item);
    if (type == typeof(int))
    {
        int number = (int)TypedReference.ToObject(item);
        Console.WriteLine("Type: {0}, Number: {1}", type, number);
    }
    else
    {
        string value = (string)TypedReference.ToObject(item);
        Console.WriteLine("Type: {0}, Text: {1}", type, value);
    }
}

Pozostaje jeszcze jedno słówko kluczowe- __makeref. Służy ono do tworzenia obiektów typu TypedReference. Przykładowo wypisanie wartości obiektu może wyglądać następująco:

int i = 8;
PrintTypeReference(__makeref(i));

Należy także pamiętać, że TypedRefenece przechowywuje zarówno typ jak i zarządzany wskaźnik, więc modyfikacja obiektu typy TypedReference może mieć odzwierciedlenie w innym fragmencie kodu. Przykładowo kod inkrementujący zmienną może wyglądać następująco:

public static void Main(string[] args)
{
    int i = 8;
    Inc(__makeref(i));
    Console.WriteLine(i);
}

public static void Inc(TypedReference value)
{
    __refvalue(value,int)++;
}

Oczywiście powyższy kod wypisze 9 na konsoli.

Na koniec jeszcze jeden ciekawy przykład użycia __makeref wraz z refleksją:

public struct Point
{
    public int X;
    public int Y;

    public override string ToString()
    {
        return X + ", " + Y;
    }
}

public static void Main(string[] args)
{
    Point point = new Point() { X = 1, Y = 2 };
    Console.WriteLine(point);

    FieldInfo fieldInfo = typeof(Point).GetField("X");
    fieldInfo.SetValue(point, 3);
    Console.WriteLine(point);

    fieldInfo.SetValueDirect(__makeref(point), 4);
    Console.WriteLine(point);
}

Oczekiwalibyśmy od tego kodu, że w jako druga lina wyświetlone zostanie "3, 2". Niestety sygnatura SetValue jako pierwszy parametr posiada parametr typu object, więc dojdzie tu do niechcianego boxingu, a oczekiwana zmienna point nie zostanie zmodyfikowana. Problem ten rozwiązuje dopiero użycie SetValueDirect.

Nasuwa się jeszcze pytanie: używać, czy nie używać? Skoro są to nieudokumentowane elementy języka istnieje prawdopodobieństwo, że mogą zniknąć wraz z kolejną wersją kompilatora. Z drugiej jednak strony twórcy tego oprogramowaina mogą nie chcieć utracić kompatybilności z już istniejącym kodem, który potencjalnie może z nich kożystać. Generalnie przedstawiam to jako ciekawostkę i nie polecam używania w produkcyjnym kodzie.

Nie rób tego w domu pracy.

BadImageFormatException - problem z 32 i 64 bitowymi bibliotekami po raz N-ty?

Wystąpienie BadImageFormatException, wśród doświadczonych developerów, rodzi pytanie czy przez przypadek nie została załadowana zewnętrzna biblioteka w złej wersji- 32 lub 64 bitowej. Nie jest to jednak jedyna przyczyna występowania tego wyjątku. Na początek jednak przyjrzyjmy się kawałkowi prostego kodu:

using System;
using System.Diagnostics;

namespace BadImageFormatExceptionSample
{
    public class Data
    {
        public string Text { get; set; }
    }

    public class Base<T> where T : Data
    {
        public virtual T Print(T t)
        {
            Log(() =>
            {
                Console.WriteLine(t.Text);
            });
            return t;
        }

        protected virtual void Log(Action action)
        {
            var stackTrace = new StackTrace();
            var method = stackTrace.GetFrame(1).GetMethod();
            Console.WriteLine("Type: {0}, Method: {1}; before", 
                method.DeclaringType.Name, method.Name);
            action();
            Console.WriteLine("Type: {0}, Method: {1}; after", 
                method.DeclaringType.Name, method.Name);
        }
    }

    public class TestClass : Base<Data>
    {
        public override Data Print(Data data)
        {
            Log(() =>
            {
                data = base.Print(data);
            });

            return data;
        }
    }

    class Program
    {
        static void Main()
        {
            var z = new TestClass();
            z.Print(new Data() { Text = "Test" });
        }
    }
}

Kod jest stosunkowo prosty. Można by dać go jako rozgrzewkę osobie na rozmowie kwalifikacyjnej i spytać co program wypisze na ekranie. Jako wyjście spodziewalibyśmy się tekstu podobnego do:

Type: TestClass, Method: Print; before
Type: Base`1, Method: Print; before
Test
Type: Base`1, Method: Print; after
Type: TestClass, Method: Print; after

Niestety, wbrew wszelkim oczekiwaniom pojawi się tu wyjątek, który jest bohaterem tego artykułu. Dla dopełnienia prezentuję początek wyjścia z programu:

Type: TestClass, Method: Print; before

Unhandled Exception: System.BadImageFormatException: An attempt was made to load 
    a program with an incorrect format. (Exception from HRESULT: 0x8007000B)

Na pierwszy rzut oka ciężko wskazać co jest nie tak, jednak przy dłuższej analizie zdekompilowanego kodu można dojść do wniosku, że coś jest nie w porządku. Problem da się łatwo obejść; jeśli wystąpił wystarczy użyć mechanizmu delegatów i wszystko będzie działać jak na począku zakłądaliścy:

public override Data Print(Data data)
{
    var print = new Func<Data, Data>(base.Print);
    Log(() =>
    {
        data = print(data);
    });
    return data;
}

Całe szczęście problem nie występuje często - trzeba użyć kilku bardziej zaawansowanych mechanizmów języka by problem wystąpił. Problem został naprawiony w kompilatorze nadchodzącym z wersją 2012 Visual Studio, więc możemy spać spokojnie i dalej śmiało używać wyrażeń lambda, jak tylko wyobraźnia nam pozwoli. Dociekliwym przedstawiam jeszcze kod IL wygenerowany przez Visual Studio 2012:

 

.method public hidebysig instance void  '<Print>b__0'() cil managed
{
  // Code size       25 (0x19)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldarg.0
  IL_0003:  ldfld      class BadImageFormatExceptionSample.TestClass BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::'<>4__this'
  IL_0008:  ldarg.0
  IL_0009:  ldfld      class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::data
  IL_000e:  call instance class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass::'<>n__FabricatedMethod3'(class BadImageFormatExceptionSample.Data)
  IL_0013:  stfld      class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::data
  IL_0018:  ret
} // end of method '<>c__DisplayClass1'::'<Print>b__0'

Oraz kłopotliwa wersja z 2010 dla porównania(różnica w lini IL_000e):

.method public hidebysig instance void  '<Print>b__0'() cil managed
{
  // Code size       25 (0x19)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldarg.0
  IL_0003:  ldfld      class BadImageFormatExceptionSample.TestClass BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::'<>4__this'
  IL_0008:  ldarg.0
  IL_0009:  ldfld      class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::data
  IL_000e:  call instance !0 BadImageFormatExceptionSample.TestClass::'<>n__FabricatedMethod3'(!0)
  IL_0013:  stfld      class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::data
  IL_0018:  ret
} // end of method '<>c__DisplayClass1'::'<Print>b__0'

Na koniec rodzi się jeszcze jedna myśl- czy nie za bardzo ufamy narzędziom z których korzystamy? Kompilatory tworzą też ludzie i mogą popełniać błędy, więc do każdego kodu należy podchodzić z pewną rezerwą- mniejszą bądź większą, w zależności od źródła. Taka programistyczna zasada ograniczonego zaufania.