Jasica.Net

C#, .Net, SQL i nie tylko

Sprawdzanie atrybytów

Istnieje kilka sposobów sprawdzania obecności atrybutów w .Net. Najpopularniejsze to metody IsDefined oraz GetCustomAttribute. Oczywiście drugą z nich można wykorzystać do sprawdzania atrybutu poprzez  porównanie zwracanej wartości z null'em. Poniżej prezentuję krótki kod porównujący wydajność obu rozwiązań.

    class Program
    {
        private static Type type = typeof(Test);

        private static Type attributeType = typeof(TestAttribute);

        private static void MeasureTime(Func<bool> action)
        {
            var watch = Stopwatch.StartNew();
            for(var i = 0; i < 1000; i++)
            {
                action ();
            }

            watch.Stop();

            Console.WriteLine(watch.ElapsedTicks);
        }

        static void Main()
        {
            RuntimeHelpers.PrepareMethod(typeof(Program).GetMethod("IsDefinded", BindingFlags.Public | BindingFlags.Static).MethodHandle);
            RuntimeHelpers.PrepareMethod(typeof(Program).GetMethod("GetCustomeAttributes", BindingFlags.Public | BindingFlags.Static).MethodHandle);

            MeasureTime(IsDefinded);
            MeasureTime(GetCustomeAttributes);
        }

        public static bool IsDefinded()
        {
            return type.IsDefined(attributeType, false);
        }

        public static bool GetCustomeAttributes()
        {
            return type.GetCustomAttribute(attributeType, false) != null;
        }
    }

    [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]
    public sealed class TestAttribute : Attribute
    {
        private readonly string positionalString;

        public TestAttribute(string positionalString)
        {
            this.positionalString = positionalString;
        }

        public string PositionalString
        {
            get
            {
                return positionalString;
            }
        }

    }


    [TestAttribute("SomeText")]
    public class Test
    {
        
    }

Uruchamiając kod na testowej maszynie uzyskałem rezultaty 3.548 oraz 10.607. Widać wyraźnie, że różnica jest znaczna, co w przypadku przeglądania całego assebly pod kątem obecności typów posiadających dany atrybut może wyraźnie skrócić czas operacji.

Na koniec chciałbym wspomnieć o jeszcze jednej metodzie pozwalającej pobrać atrybut -GetCustomAttributesData. Jest ona o tyle przydatna, gdyż nie powoduje utworzenia instancji atrybutu, a pozwala na wygodne sprawdzenie jakie parametry są przekazywane do konstruktora i właściwości, ale o tym innym razem

Dostęp do właściwości obiektów anonimowych bez refleksji

W tym wpisie pragnę zaprezentować kolejną ciekawą właściwość języka C#- tym razem odnosić się będzie ona do anonimowych obiektów.


Spójrzmy, więc na poniższy kod:


        static object GetAnonymousObject()
        {
            return new { Foo = 1, Bar = 2 };
        }

        static void Main(string[] args)
        {
            var obj = GetAnonymousObject();
            var something = ...;

            Console.WriteLine("Foo={0}, Bar={1}", something.Foo, something.Bar);
        }

Co należyt wstawić w miejsce kropek, aby kod kompilował się, a także nie powodował błędów w czasie wykonywania? Przykładowe rozwiązanie prezentuje poniżej.


        static void Main(string[] args)
        {
            var obj = GetAnonymousObject();
            var something = Cast(obj, new { Foo = 0, Bar = 0, Z = 0});

            Console.WriteLine("Foo={0}, Bar={1}", something.Foo, something.Bar);
        }

        static T Cast<T>(object obj, T tmpInstance)
        {
            return (T) obj;
        }


Analizując powyższy kod można zacząć się zastanawiać co pozwala mu działać nie powodując wyjątków rzutowania. Stoi za tym optymalizacja kompilatora, która dba o to by nie generować zbędnych typów anonimowych zawierających takie same zestawy właściwości. Warto, więc zauważyć, że dodanie lub usunięcie jakiegokolwiek pola z typu spowoduje rzucenie wyjątku nieprawidłowego rzutowania.

 

Jak wspomniałem na początku, jest to ciekawostka i należy podchodzić do niej ostrożnie. Zazwyczaj użycie standardowych mechanizmów refleksji czy obiektów dynamicznych lepszym rozwiązaniem.

Ukrywanie kodu przed narzędziami dbajacymi o jakość

W większych projektach często są wykorzystywane różne narzędzia generujące kod, które pozwalają zaoszczędzić czas programisty. Mogą one robić to poprzez tworzenie kodu źródłowego lub bezpośrednio bibliotek. Często jednak taki kod może być kłopotliwy dla narzędzi dbających o jakość.

Jeżeli kod jest generowany automatycznie może się okazać, że metryki typu Maintainability Index są stosunkowo niskie. W normalnym wypadku popsułoby to jedynie statystyki, ale jeżeli wykorzystujemy któreś z metryk w procesie ciągłej integracji, jako strażnika broniącego przed kodem niskiej jakości może się okazać, że takowy kod będzie problematyczny. Mankament ten można łatwo wyeliminować oznaczając wygenerowaną metodę atrybutem GeneratedCode. Dodatkowo jako parametr można przekazać nazwę narzędzia i wersję, które zostało wykorzystane do stworzenia kodu. Jeden atrybut i build server nie będzie sprawiał kłopotów. 

Analogicznie sprawa wygląda z wykluczeniem z pokrycia kodu- wystarczy dodać atrybut DebuggerNonUserCode. W tym przypadku jednak warto zastanowić się czy na pewno chcemy mieć kod w repozytorium, który nie jest weryfikowany przez żadne testy.

Style Cop można poprosić o ignorowanie kodu na kilka sposobów: poprzez dodanie w nagłówku pliku komentarza zawierającego "<auto-generated />" lub poprzez stworzenie regionu o nazwie zawierającej "generateg code".

Podsumowując czasami spotykanie niedogodności nie powinny być wymówką do zaniechania wdrażania narzędzi podnoszących jakość kodu, gdyż większość z nich można obejść.

Zabawy z null'em

Post ma na celu ukazanie kilku ciekawych własności C# i CLR, o których niewielu programistów pamiętam. 

Czasami metoda do sprawdzania czy dany obiekt jest pusty mogłaby być pomocna:

        public class SimpleClass
        {
            public bool IsNull()
            {
                return this == null;
            }
        }

        static void Main(string[] args)
        {
            SimpleClass sc = null;
            if (sc.IsNull())
                Console.WriteLine("null");
            else
                Console.WriteLine("not null");
        }

Oczywiście ten kod nie będzie działać poprawnie, a każde takie wywołanie skończy się rzuceniem wyjątku NullReferenceException. Jako, że metoda nie wykorzystuje żadnych składowych klasy można zastosować pewną sztuczkę. Wygenerowany kod il metody Main będzie wyglądał następująco:

.method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    .maxstack  2
    .locals init ([0] class Null.Program/SimpleClass sc,
             [1] bool CS$4$0000)
    IL_0000:  nop
    IL_0001:  ldnull
    IL_0002:  stloc.0
    IL_0003:  ldloc.0
    IL_0004:  callvirt   instance bool Null.Program/SimpleClass::IsNull()
    IL_0009:  ldc.i4.0
    IL_000a:  ceq
    IL_000c:  stloc.1
    IL_000d:  ldloc.1
    IL_000e:  brtrue.s   IL_001d

    IL_0010:  ldstr      "null"
    IL_0015:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_001a:  nop
    IL_001b:  br.s       IL_0028

    IL_001d:  ldstr      "not null"
    IL_0022:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0027:  nop
    IL_0028:  ret
  }

Na uwagę zasługuje linia oznaczona IL_0004- wystarczy w niej podmienić wywołanie callvirt na call, a w czasie uruchomienia na konsole zostanie wypisany komunikat "null". Dzieje się tak, gdyż jedną z różnic pomiędzy tymi dwoma sposobami wywoływania metod jest to, że ta druga nie sprawdza, czy obiekt, na którym wołamy metodę nie jest nullem. Call jest stosowany głównie przy wywoływaniu metod statycznych, jednak w tym przypadku może się nadać.

Ręczna edycja nie jest zbyt miła- teoretycznie można skorzystać z narzędzi typu Fody, do zautomatyzowania tej pracy. Istnieje jednak prostszy sposób na otrzymanie podobnego efektu- extensions method:

 

    public static class SimpleClassExtensions
    {
        public static bool IsNull(this SimpleClass instance)
        {
            return instance == null;
        }
    }

 Inny ciekawy przypadek stanowią dwie poniższe metody:

        public static string GetDefaultString<T>() where T : new()
        {
            T instance = new T();
            return instance.ToString();
        }

        public static Type GetInstanceType<T>() where T : new()
        {
            T instance = new T();
            return instance.GetType();
        }

Pierwsza z nich zawsze będzie działać prawidłowo. Drugą można spróbować popsuć wołając np. GetInstanceType<long?>, czego wynikiem będzie niespodziewane rzucenie wyjątku NullReferenceException. Wszystkiemu winny jest kod wygenerowanej metody:

.method public hidebysig static class [mscorlib]System.Type 
        GetInstanceType<.ctor T>() cil managed
{
  .maxstack  1
  .locals init ([0] !!T 'instance',
           [1] class [mscorlib]System.Type CS$1$0000,
           [2] !!T CS$0$0001)
  IL_0000:  nop
  IL_0001:  ldloca.s   CS$0$0001
  IL_0003:  initobj    !!T
  IL_0009:  ldloc.2
  IL_000a:  box        !!T
  IL_000f:  brfalse.s  IL_001c
  IL_0011:  ldloca.s   CS$0$0001
  IL_0013:  initobj    !!T
  IL_0019:  ldloc.2
  IL_001a:  br.s       IL_0021
  IL_001c:  call       !!0 [mscorlib]System.Activator::CreateInstance<!!0>()
  IL_0021:  nop
  IL_0022:  stloc.0
  IL_0023:  ldloca.s   'instance'
  IL_0025:  constrained. !!T
  IL_002b:  callvirt   instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
  IL_0030:  stloc.1
  IL_0031:  br.s       IL_0033
  IL_0033:  ldloc.1
  IL_0034:  ret
}

Jest to metoda generyczna, więc aby mogła ona działać prawidłowo zarówno z klasami jak i strukturami kompilator przed wywołaniem metody instrukcją constrained. W przypadku metody ToString problem nie powstanie, gdyż jest ona wirtualna. Niestety GetType nie ma takiej własności i zostanie zastosowany boxing, a tym samym ostatecznie wywołanie metody na null'u.

To już koniec ciekawostek na dzień dzisiejszy. Zapraszam w przyszłości.

Dynamiczny wybór wersji pomiędzy wersjami x86 oraz x64 natywnych bibliotek

Targetowanie projektu na architekturę x86 przy współpracy z natywnymi bibliotekami może nie być eleganckim rozwiązaniem, zwłaszcza jeśli dostępna jest w wersji 32 i 64 bitowej. Można tego uniknąć dokonując wyboru wersji biblioteki natywnej w czasie wykonywania programu.

Podobny problem w świecie kodu zarządzanego można by rozwiązać przy pomocy zdarzenia AssemblyResolve AppDomeny, która miałaby załadować kod. Co zrobić jednak, gdy biblioteka jest tylko wraperem na natywne wywołania? Należy w katalogu z plikami wykonywalnymi stworzyć dwa dodatkowe katalogi, w których znajdą się biblioteki skąpilowane na daną platformę(np. "\x64" i "\x32"). Natomiast czy aplikacja działa jako proces 32 czy 64 bitowy można sprawdzić przy pomocy właściwość Is64BitProcess klasy Environment. Na koniec należy jeszcze wskazać na podstawie wcześniej poczynionych kroków, gdzie system powinien szukać bibliotek. Najprościej to uczynić przy pomocy natywnej funkcji SetDllDirectory. Przykładowy kod może wygląda tak:

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool SetDllDirectory(string lpPathName);

        private static void Init()
        {
            var location = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
            var type = Environment.Is64BitProcess ? "x64" : "x86";
            var path = Path.Combine(location, type);
            SetDllDirectory(path);
        }

Corupted State Exception

.Net w wersji 4.0 wprowadził pojęcie Corupted State Exception. W odróżnieniu od pozostałych wyjątków, nie są one wyłapywane przez bloki catch/finally. Takie zachowanie ma jak najbardziej sens, gdyż w większości sytuacji stanowią one sygnał, że w programie stało się coś złego i dalsze wykonywanie może nie mieć sensu. Występują one głównie przy współpracy z natywnymi bibliotekami. Poniższe wyjątki mogą być traktowane jako CSE:

  • STATUS_ACCESS_VIOLATION
  • STATUS_STACK_OVERFLOW
  • EXCEPTION_ILLEGAL_INSTRUCTION
  • EXCEPTION_IN_PAGE_ERROR
  • EXCEPTION_INVALID_DISPOSITION
  • EXCEPTION_NONCONTINUABLE_EXCEPTION
  • EXCEPTION_PRIV_INSTRUCTION
  • STATUS_UNWIND_CONSOLIDATE

Przeglądając tą listę możemy zauważyć, że w większości wypadków sugerują one błędy w kodzie natywnym. Poniższy kod C++ generuje błędne odwołanie do fragmentu pamięci, które posłuży jako przykład:

extern "C" __declspec(dllexport) 
int Add(int a, int b)
{
    int * t = 0;
    *t = 1;
    return a + b;
}

Kod tej funkcji w C# można wywołać stosując atrybut DllImport:

[DllImport("test.dll")]
public static extern int Add(int a, int b);

static void Main(string[] args)
{
     try
     {
         int result = Add(1, 2);
         Console.WriteLine(result);
     }
     catch (Exception ex)
     {
         Console.WriteLine("Cath: " + ex.Message + ex.StackTrace);
     }
     finally
     {
         Console.WriteLine("Finally");
     }
     Console.WriteLine("End");
}

Wynik działania powyższego kodu będzie różny w zależności od wybranej wersji CLR. W 4.0 nie wykona się zarówno blok catch jak i finally, natomiast w wcześniejszych wersjach wyjątek zostanie złapany, a program wykona się do końca. Wyjątki tego typu można wyłapywać CLR 4.0 o ile programista świadomie oznaczy metodę atrybutem HandleProcessCorruptedStateExceptions. Może to mieć znaczenie, gdy biblioteka zawiera dobrze znany błąd i nie da się go wyeliminować w danej chwili inaczej. Zachowanie to można wyłączyć także dla całego kodu, bez dokonywania jakikolwiek zmian w nim. Zachowanie kompatybilności wstecz można wymusić dodając wpis w pliku konfiguracyjnym:

<configuration>
    <runtime>
        <legacyCorruptedStateExceptionsPolicy enabled="true"/>
    </runtime>
</configuration>

Corupted State Exception chronią mniej świadomych programistów przed kontynuowaniem wykonywania kodu, gdy aplikacja znalazła się w złym stanie i dalsze operacje mogą prowadzić do utraty bądź uszkodzenia ważnych danych. Niestety, często w aplikacjach znajdują się zbyt ogólne bloki catch, łapiące wyjątki każdego typu, co zamiast zapewniać stabilność aplikacji, mogą prowadzić do nieoczekiwanych rezultatów. CAS chronią przed tego typu błędami i jednocześnie pozwalają wymusić wyłapywanie ich o ile zachodzi taka konieczność przy pomocy wspomnianego wcześniej atrybutu- HandleProcessCorruptedStateExceptions.

Szybki sposób na generowanie klas z JSON i XML

Zapewne nie raz spotkaliście się z potrzebą stworzenia klas odzwierciadlających pewne dane XML lub JSON. W takich przypadkach najlepiej posłużyć się odpowiednimi generatorami, by zaoszczędzić sporo czasu na tworzeniu kodu.

Pomocny może być dodatek Web Essentials 2012. Dzięki niemu teraz zawsze będziemy mieli taki generator pod ręką- dosłownie. Wystarczy kliknąć prawym przyciskiem myszy odpowiednią opcje z menu kontekstowego i JSON ze schowa zamieni się w piękną klasę:

 

W analogiczny sposób można też obsłużyć XMLa.

Polecam zapoznanie się z pozostałymi możliwościami dodatku.

MSDeploy(aka WebDeploy) - problem po aktualizacji do .Net 4.5

Migracja do .Net 4.5, mimo iż miał być bezproblemowa, czasem stwarza problemy. Jednym z narzędzi jakie nie chciało od razu współpracować z .Net 4.5 jest MSDeploy(aka WebDeploy).

Po migracji projektu do .Net 4.5 MSDeploy podczas publikacji paczki na IIS, zgłaszał błąd:

Error Code: 51
More Information: The application pool that you are trying to use has the
'managedRuntimeVersion' property set to 'v4.0'. This application requires 'v4.5'.
Error count: 1.

Mało eleganckim obejściem jest oszukanie MSDeploy'a poprzez zmianę wersji .Net przed publikacją przy pomocy przełącznika preSync:

-preSync:runCommand="%systemroot%\system32\inetsrv\APPCMD stop apppool P  
 & %systemroot%\system32\inetsrv\APPCMD set apppool P /managedRuntimeVersion:v4.5"

Wykonanie tego polecenia wprowadzi zmiany w ustawieniach puli, a nawet nowa wersja frameworka będzie dostępna do wyboru na liście:

 

Oczywiście, tak ustawiona pula nie będzie działać poprawnie, więc należy przywrócić ustawienia pierwotne, prosząc MSDeploy'a by wykonał na zakończenie porządki:

-postSync:runCommand="%systemroot%\system32\inetsrv\APPCMD set apppool P /managedRuntimeVersion:v4.0  
 & %systemroot%\system32\inetsrv\APPCMD start apppool P

Zna ktoś inne rozwiązanie tego problemu?

SignalR - czyli jak łatwo przesłać informacje o zdarzeniu do klienta

Cykliczne odpytywanie serwera może być problematyczne i mało wydajne. Istnieje jednak biblioteka SignalR, która znacząco upraszcza przesyłanie informacji o zdarzeniach z serwera do przeglądarki.

Biblioteka bardzo dużo odwala za programistę- przykładowy czat można napisać używając zaledwie kilkudziesięciu lini kodu. Zachęcam do zapoznania się przykładami.

Warto też zauważyć, że biblioteka nie ogranicza nas co do stosowanych technologi- klientem nie musi być przeglądarka(może to być dowolna aplikacja .Net'owa), a serwer nie musi wykorzystywać Asp.Net'owych handlerów do działania- możemy hostować serwer w dowolnej aplikacji, choćby w konsoli.

Jedyną wadą biblioteki jest to, że nie wspiera standardowo skalowania na wiele węzłów, a tym samym zwiększonej niezawodności. Można to jednak uzyskać poprzez wykorzystanie Windows Azure Service Bus, przez co sam projekt wydaje się być obiecujący. Wsparcie biblioteki nie ogranicza się tylko do okolic platformy .Net. Na githubie można znaleźć biblioteki mający na celu dodanie wsparcia dla iOS i Mac, a także Androida.

Biblioteka posiada całkiem spore możliwości, a na dodatek całkiem silne wsparcie społeczności. Uważam, że z tych wszystkich powodów warto poświecić jej chwile i zapoznać się z nią.

Tworzenie obiektów bez wykonywania konstruktora

Tworzenie obiektów w .Net jest utożsamiane z zarezerwowaniem dla nich pewnej pamięci, a następnie wykonaniem konstruktora(domyślnego lub parametryzowanego). Jednak w pewnych przypadkach wykonywanie kodu zawartego w konstruktorze może być niepożądane. Istnieje jednak sposób by tego uniknąć.

Brak angażowania logiki w konstruktorze jest zazwyczaj pożądany podczas odtwarzania obiektu np. po odczycie danych z dysku twardego lub danych przesłanych przez sieć. Dodatkowo, jeśli obiekt posiada bezparametryczny konstruktor utworzenie obiektu danego typu przy pomocy refleksji nie jest trudne. Sprawa staje się bardziej skomplikowana jeśli obiekt wymaga do inicjalizacji innych danych. W takim przypadku z pomocą przychodzi statyczna metoda FormatterServices.GetUninitializedObject, która jako jedyny parametr przyjmuje typ obiektu jaki chcemy utworzyć. Należy uważać z jej użyciem, gdyż tworzy ona obiekt, którego przestrzeń w pamięci jest wypełniona samymi zerami. Może implikować stan obiektu, który nie jest dla niego dozwolony i może objawiać się niepożądanym działaniem . Taki obiekt należy jak najszybciej wypełnić danymi przed jego wykorzystaniem. Przykład użycia:

    public class Foo
    {
        private int bar;
        private int baz;

        public Foo(int bar)
        {
            this.bar = bar;
            this.baz = 8;
        }

        public override string ToString()
        {
            return string.Format("bar={0}, baz={1}", this.bar, baz);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var test = (Foo)FormatterServices.GetUninitializedObject(typeof(Foo));
            Console.WriteLine(test);
        } 
    }

Na ekranie, zgodnie z oczekiwaniami, zostanie wypisana informacja, z której wynika, że obiekt nie został zainicjalizowany- "bar=0, baz=0".

Samo działanie metody jest interesujące- jak obchodzi ona taki element języka jak wywołanie konstruktora? Dekompilując jej kod nie dowiemy się dużo:

    [SecurityCritical]
    public static object GetUninitializedObject(Type type)
    {
      if (type == null)
        throw new ArgumentNullException("type");
      if (type is RuntimeType)
        return FormatterServices.nativeGetUninitializedObject((RuntimeType) type);
      throw new SerializationException(Environment.GetResourceString(
        "Serialization_InvalidType", 
        new object[1] { (object) type.ToString() }));
    }

    [MethodImpl(MethodImplOptions.InternalCall)]
    [SecurityCritical]
    private static extern object nativeGetUninitializedObject(RuntimeType type);

Oznaczenie metody jako extern wraz z MethodImplOptions.InternalCall mówi, że metoda jest zaimplementowana wewnątrz CLR. Jeśli chcemy pogrzebać głębiej zawsze można zerknąć na kod SSCLI

FCIMPL1(Object*, ReflectionSerialization::GetUninitializedObject, ReflectClassBaseObject* objTypeUNSAFE) {
    CONTRACTL {
        THROWS;
        DISABLED(GC_TRIGGERS);
        MODE_COOPERATIVE;
        SO_TOLERANT;
    }
    CONTRACTL_END;
    
    OBJECTREF           retVal  = NULL;
    REFLECTCLASSBASEREF objType = (REFLECTCLASSBASEREF) objTypeUNSAFE;

    HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_NOPOLL(Frame::FRAME_ATTR_RETURNOBJ);

    if (objType == NULL) {
        COMPlusThrowArgumentNull(L"type", L"ArgumentNull_Type");
    }

    TypeHandle type = objType->GetType();

    // Don't allow arrays, pointers, byrefs or function pointers.
    if (!type.IsUnsharedMT())
        COMPlusThrow(kArgumentException, L"Argument_InvalidValue");

    MethodTable *pMT = type.GetMethodTable();
    PREFIX_ASSUME(pMT != NULL);

    //We don't allow unitialized strings.
    if (pMT == g_pStringClass) {
        COMPlusThrow(kArgumentException, L"Argument_NoUninitializedStrings");
    }

    // if this is an abstract class or an interface type then we will
    //  fail this
    if (pMT->IsAbstract()) {
        COMPlusThrow(kMemberAccessException,L"Acc_CreateAbst");
    }
    else if (pMT->ContainsGenericVariables()) {
        COMPlusThrow(kMemberAccessException,L"Acc_CreateGeneric");
    }

    // Never allow the allocation of an unitialized ContextBoundObject derived type, these must always be created with a paired
    // transparent proxy or the jit will get confused.
    if (pMT->IsContextful())
        COMPlusThrow(kNotSupportedException, L"NotSupported_ManagedActivation");

    // If it is a nullable, return the underlying type instead.  
    if (Nullable::IsNullableType(pMT)) 
        pMT = pMT->GetInstantiation()[0].GetMethodTable();
 
    retVal = pMT->Allocate();

    HELPER_METHOD_FRAME_END();
    return OBJECTREFToObject(retVal);
}
FCIMPLEND

Jest tu sprawdzana cała masa warunków, aż ostatecznie alokowana jest po prostu pamięć - nie ma tu żadnego wołania konstruktora.