Próbuję wyrobić sobie nawyk pozostawienia komentarza po konferencjach/prezentacjach, na których byłem zatem po poście na temat WG.NET 85 pora na spotkanie 86!
Simple.Data - Piotr Rabiniak
Ciekawa tematyka, jak rozumiem rozwinięcie wpisu na ten sam temat. Simple.Data nie używałem ale słyszałem o nim już nie raz. Idea dynamica zaprzęgniętego by ominąć cały ten ciężki ORMowy wagon jest w istocie fajna:
var users = Database.Open.Users.FindAllByNameAndAge("Kowalski", 40);
I już! Na podstawie nazwy metody, parametrów itd. wygenerowany zostanie odpowiednie zapytanie do tego cośmy podpięli jako Database (a może to być m.in. Oracle, SQL Server, MySQL, PostreSQL, MongoDB itd.). Fajne? Fajne. Co więcej dostajemy z pudełka mapowanie, jeśli zamiast vara użyjemy nasze POCO:
List<User> = Database.Open.Users.FindAllByNameAndAge("Kowalski", 40);
Oczywiście to nie wszystko, mamy wiele innych metod, więc budujemy bardziej skomplikowane zapytania:
IEnumerable<string> = db.Users
.FindAllByCityID(1)
.Where(db.Users.Age > 18)
.Select(db.Users.Name);
Myślę, że świetnie się to sprawdza w aplikacjach z prostym, CRUDowym dostępem do danych. Mamy też Joiny itd. itp. I tu zaczynają się moje wątpliwości - jeśli korzystanie z bazy danych staje się bardziej skomplikowane, zacząłbym pisać procedury składowane, zamiast takich wielolinijkowych tworów. Ale, i tego zabrakło na prezentacji, tutaj też może nam pomóc Simple.Data. Jeśli np. mamy procedurę składowaną:
CREATE PROCEDIRE GetMyFavouriteUsers
@City VARCHAR(MAX)
@Country VARCHAR(MAX)
...
wystarczy:
List<User> = db.GetMyFavouriteUsers(City: "Warsaw", Country: "Poland");
Bardzo przyjemnie się też to mockuje, co ułatwić powinno testowanie:
Database.UseMockAdapter(new InMemoryAdapter());
Standardowo wypełniamy bazę danymi:
var db = Database.Open();
db.Users.Insert(Id: 1, Name: "Jan Kowalski");
I dalej już wykonujemy swoje zapytania.
W nadchodzącej wersji 2.0 pojawią się asyncki, co jest bardzo miłym dodatkiem:
var users = await db.Users.FindAllByNameAndAge("Kowalski", 40);
Nie wiem jak by to wszystko sprawdziło się w większym projekcie. Brak Intellisense rodzić może głupie literówki. Zgadzam się też z Piotrem, że pewną wadą jest, iż musimy się nauczyć nowej składki "zapytań", zamiast używać tej z LINQ. Z drugiej strony, lekkość i wygoda użycia tego tworu zachęca, składni zaś się przecież nauczymy jeśli w projekcie sporo tego używamy.
TPL Dataflow - Szymon Warda
Prezentacja szybko powiedziana:) Ale wyraźnie i jasno. Duży plus za cenną uwagę by nie używać TPL Dataflow do porządkowania kodu, bo to wydaje mi się największa pokusą jak się o tym usłyszy. Sama technologia jest super, choć chyba trochę niedoceniana i pomijana.
Po prezentacji zacząłem się zastanawiać nad realnymi przykładami jej użycia. I nie jest tak, że przychodzi ich od razu mnóstwo do głowy. Trudno wpasować poziom abstrakcji oferowany przez tę bibliotekę pomiędzy po prostu użycie TPL, a bardziej skomplikowanymi przypadkami gdzie workflow staje się biznesowy i lepiej opisywalny np. w Wokrflow Foundation. Albo na tyle rozproszony, że warto już myśleć o Akka.NET czy Orleans. Może to jedna z przyczyn małej popularności tej biblioteki. Na razie wciąż wymyślam jakieś konkretne przykłady zastosowań, może macie swoje pomysły i/lub przykłady z życia wzięte?
Swoją drogą, chyba warto przy okazji wspomnieć o DataflowEx (https://github.com/gridsum/dataflowex), bardziej obiektowej nadbudówce TPD Dataflow, m.in. do wygodniejszego tworzenia reużywalnych klas reprezentujących nasze workflowy.
Sizeof(?) - Szymon Kulec
Ciekawy, krótki snack na temat poznania rozmiaru typów nullowalnych w pamięci - w kontekście tworzenia zero-time serializerów. Szymon wybrał pewien sposób mierzenia tego rozmiaru: wołanie prywatnego Marshal.SizeOfHelper na typie, ponieważ ani operator sizeof, ani Marshal.SizeOf nie działają na typach generycznych.
Przypomnijmy, że definicja Nullable<> jest bardzo prosta:
public struct Nullable<T> where T : struct
{
private bool hasValue;
internal T value;
...
}
Znalezione rozmiary struktur mają odpowiednio (w bajtach):
Nullable<int> - 8
Nullable<long> - 16
Nullable<bool> - 8
Nullable<Guid> - 20
Takie rozmiary Szymon tłumaczył alignementem do 8 bajtowego słowa (w przypadku maszyn 64 bitowych) co wydaje się dobrze tłumaczyć trzy pierwsze przypadki, jednak 20 już przez "8 się nie dzieli" i jest konsternacja. Jednak intuicja podpowiada mi, że alignement obowiązuje wobec offsetów kolejnych pól, nie całego rozmiaru. Warto przypomnieć, że by default, struktury używają StructLayoutAttribute o wartości LayoutKind.Sequential. MSDN opisuje je jako:
LayoutKind.Sequential: By default, this is the value of StructLayoutAttribute used by structs. This layout indicates that all of the fields in the type are laid out in memory in the same order that they appear in the type definition, but spaced such that each field starts on a byte offset (within the type) that is divisible by the size of the field. For example, ints and object references are placed on 4-byte divisible offsets (0, 4, 8, etc. — also known as “4-byte boundaries”), shorts and chars are placed on 2-byte boundaries, and bytes and bools can be placed at any location (“1-byte boundaries”).
Co więcej, StructLayoutAttribute ma parametr Pack (domyślnie wynoszący 8):
Packing values of 2 and higher will cause each field to be aligned on a byte offset relative to the beginning of the structure. Therefore, data fields will start on offsets that are multiples of the requested packing value.
Moim skromnym zdaniem te definicje są nawet sprzeczne. Czy to tłumaczy powyższe rozmiary? Średnio:
- int? - bool leży na początku ("1-byte boundary") ale już kolejny int jest na offsecie 4-bajtowym ("4-byte boundary") zatem sumarycznie to 8 bajtów (a nie dlatego, że bool ma w pamięci 4 bajty, ma tyle dopiero po "zmarshalowaniu"). Wygląda na to, że 8 bajtowy packing jest tu ignorowany, zatem reguła mogłaby brzmieć: offset pola wynosi wielkrotność packing LUB rozmiaru typu, zależy co ma mniejszą wartość. I tego się trzymajmy w dalszych rozmyślaniach.
- long? - choć nie jest to powiedziane wprost, można się domyślać, że long ma "8-byte boundary", zatem value zaczyna się na 8 bajcie, zajmuje 8 bajtów - mamy zatem 16 bajtów.
- bool? - tu pierwsza zagadka. Zgodnie z uknutą powyżej zasadą rozmiar powinien wynosić 2 bajty (dwa "1-byte boundary" pola). Hmm...
- Guid? - kolejna zagadka. GUID to 128-bitowy integer (16 bajtów) zatem powinien zaczynać się albo na 16 albo ew. 8 (ze względu na packing) bajcie. A zaczyna się na 4. Znowu hmm...
Może jednak używanie prywatnej metody Marshal.SizeOfHelper powinno budzic niepokój i zwraca złe wyniki? Twórcy CLR musieli mieć powód by te metody nie były dostępne dla typów generycznych. Postanowiłem zatem sprawdzić to inaczej i użyć instrukcji sizeof wyspecyfikowanej w CLI (Standard ECMA-335):
III.4.25 sizeof – load the size, in bytes, of a type
Returns the size, in bytes, of a type. typeTok canbe a generic parameter, a reference type or a value type.
oraz
sizeof returns the total size that would be occupied by each element in an array of this type –including any padding the implementation chooses to add. Specifically, array elements lie sizeof bytes apart.
No i super. Zatem piszemy prosty helperek:
public static class TypeExtensions
{
public static int SizeOf(this Type t)
{
var dm = new DynamicMethod("$", typeof(int), Type.EmptyTypes);
ILGenerator il = dm.GetILGenerator();
il.Emit(OpCodes.Sizeof, t);
il.Emit(OpCodes.Ret);
var func = (Func<int>)dm.CreateDelegate(typeof(Func<int>));
return func();
}
}
I co my tu mamy:
var s1 = typeof(Nullable<int>).SizeOf();
var s2 = typeof(Nullable<long>).SizeOf();
var s3 = typeof(Nullable<bool>).SizeOf();
var s4 = typeof(Nullable<Guid>).SizeOf();
Zwraca to nam 8, 16, 2 oraz 20. Z powyższych reguł rozwiązuje się zatem zagadka bool?. Nadal nie rozumiemy jednak Guid?. ALE.. błąd polega, że traktujemy Guid osobno, a dla CLR to jest jedna struktura wynikowa wyglądająca jako:
public struct NullableGuid
{
private bool hasValue; // fieldy z Nullable<>
private int _a; // fieldy z Guid
private short _b;
private short _c;
private byte _d;
private byte _e;
private byte _f;
private byte _g;
private byte _h;
private byte _i;
private byte _j;
private byte _k;
}
I tu już zaczyna to mieć sens! _a zaczyna się na 4 bajcie (krotność rozmiaru), każde kolejne pole też znajduje się na pozycji krotności rozmiaru.
Pozostaje pytanie otwarte - czy bool? ma 2 czy 8 bajtów?!