Mając wartość enuma w postaci napisowej, pobraną na ten przykład z bazy, bardzo łatwo jest z powrotem sparsować ją do właściwego dla aplikacji typu:
1: public enum MyEnum 2: { 3: FirstVal, 4: SecondVal 5: } 6: //... 7: (MyEnum)Enum.Parse(typeof(MyEnum), "FirstVal");
Powtarzanie tego w kodzie jest jednak dość męczące. I po raz kolejny świetny mechanizm Extension methods przychodzi na ratunek:
1: public static class StringExtensions 2: { 3: public static TEnum ToEnum<TEnum>(this string _this) where TEnum : struct 4: { 5: return (TEnum)Enum.Parse(typeof(TEnum), _this, true); 6: }
Zwracam uwagę na warunek nałożony na parametr generyczny (where TEnum : struct). Wszystkie typy wyliczeniowe dziedziczą z value-types, dzięki temu już na etapie kompilacji otrzymujemy pewną (choć nie 100%-ową) weryfikację przekazywanego typu – ponieważ "jeśli jest to typ referencyjny to nie jest enumem".
A efekt końcowy o ile ładniejszy od poprzedniego:
1: "FirstVal".ToEnum<MyEnum>()
Faaajne:) Uwielbiam takie małe i funkcjonalne kawałki kodu. Jest tylko jeden problem — gdzie taki kod umieszczać? Kopiowanie go i wklejanie do każdego projektu mi się zbytnio nie podoba. Z drugiej strony tworzenie dll-ki z jedną lub kilkoma metodami też jest takie średnie. Z trzeciej zaś — jedna wielka dll-ka, w której są wszystkie przyjemne "snippety" to też jakoś nie to. Masz w tej kwestii jakieś sprawdzone rozwiązanie?
@Szymon Pobiega:
U mnie "sprawdzonym rozwiązaniem" jest jednak kopiowanie z projektu do projektu:). Ale nie mam zbioru stałych "starterów" które dodaję już na początku. Po prostu w razie potrzeby sięgam do już stworzonych rozwiązań i umieszczam je w aktualnym projekcie. I sobie ewoluują… Z jednej strony nie jest to idealne, ale z drugiej strony – takie pomocnicze rzeczy są zwykle banalnie proste, więc w takiej powtarzalności nie widzę problemu. Na pewno wolę to niż jakąś "Procent.Common.dll":) która będzie wielkim śmietnikiem dodawanym do referencji każdego mojego projektu jako pierwsza i konieczna zależność.
Mały i elegancki helper ;) Bardzo lubie takie "zgrabne" rozwiązania!
@Procent
A czemu nie widzisz jednej duzej dll’ki? Rozumime zmienne dla aktualnego projektu ( bo sa jakos unikatowe) ale takie rzeczy jak dostep do danych (jesli masz) albo inne uzyteczne snippety/biblioteki ktore sie nie zmieniaja to czemu nie?
Pytam bo moze sa tam jakies za i przeciw ktory nie widze, bo zawsze ide Common.dll byla dla mnie akceptowala i uwazalem ja za dobra
Pozdrawiam.
Miałem w firmach kilkukrotnie styczność z takimi "common.dll". A to <firma>.Common.DataAccess.dll, a to <firma>.Common.Utils.dll, a to jeszcze inne wynalazki. Są trzy drogi:
1) każdy aktualnie realizowany (i przeszły) projekt ma własną niezależnie rozwijaną kopię -> co od kopiowania jedynie wybranych, potrzebnych w konkretnym przypadku mechanizmów różni się tylko tym że ciągniemy ze sobą masę zbędnych rzeczy;
2) mamy jedną, nieustannie (siłą rzeczy) rozwijaną "instancję" biblioteki i każdy projekt ma do niej referencje (przez externals w svn czy submodules w gicie); ograniczając się jedynie do "przyrostowego" rozwijania biblioteki tego typu może być średnio wygodne, ponieważ w takich kawałkach kodu często mogą nastąpić zmiany (ciągle da się wymyślać lepsze sposoby na rozwiązanie częstych problemów); natomiast modyfikacja już napisanego kodu może zadziałać w jednym projekcie, ale popsuć wszystkie inne… w ten sposób nawet nasze wewnętrzne API musimy traktować jako de facto udostępniony na zewnątrz framework, a taki krok chyba najlepiej odsunąć w czasie tak daleko jak to możliwe
3) mamy jedną bibliotekę, ale jej nie rozwijamy -> nawet nie będę pisał nic więcej, bo posiadanie bibliteki ‘read-only’ jest bez sensu
Dlatego właśnie wolę gdy każdy projekt jest zamkniętą całością – podczas tworzenia jestem dzięki temu w ‘project-scope’ a nie ‘all-my-projects-scope’.
Chętnie poznam argumenty z drugiej strony barykady:), ale doświadczenie nauczyło mnie że nie jest to dobra droga.
Piszesz "Mając wartość enuma w postaci napisowej, pobraną na ten przykład z bazy" – jak się zapatrujecie na przetrzymywanie stringów zamiast wartości całkowitych w bazie ? Z jednej strony fajnie bo w bazie mamy czarno na białym konkretną wartość, z drugiej napis może się zmienić a wartość pozostanie wartością. I jeszcze kilka innych argumentów za jedną czy drugą stroną się znajdzie. To jak to jest u was i dlaczego ?
Jako, że nieraz borykałem się z: a) problemem dużego przyrostu wielkości bazy b) wydajnością – to jestem za używaniem liczb. Szczególnie, że często taki enum nie wykracza poza wielkość byte, a co za tym idzie tinyint w MSSQLu.
Czytelność bazy? Skoro baza to tylko zbiornik danych, a mięsko to kod programu to kto zagląda rękami do bazy? OK, raporty… trzeba czasami napisać skomplikowaną procedurę/zapytanie rękami, ale nie ma ich tak wiele, aby sobie z tym nie poradzić. Poza tym bardziej skomplikowany raport wymaga wcześniejszego przygotowania podłoża (czyli m.in. wyciągnięcie z kodu/dokumentacji (z naciskiem na dokumentację) znaczenia dla wartości enuma.
@Dawid Kowalski:
Moim zdaniem nie ma "złotej reguły". Jeśli taki enum miałby być mapowany na najprostszą tabelę słownikową (id+nazwa) z kilkoma zaledwie elementami (np. stan zamówienia) to nie ma sensu tworzyć osobnej tabeli. Nie chodzi nawet o to że w bazie wszystko jest "czarno na białym" – po prostu tak jest prościej.
Jeśli natomiast miałyby się tam znajdować dodatkowe informacje edytowalne/konfigurowalne z poziomu UI (jak Name/DisplayName/Description czy np. nazwa typu który jest odpowiedzialny za obróbkę zamówienia w danym stanie) to osobna tabela jest wręcz niezbędna.
Tak więc – nie widzę nic złego w trzymaniu napisów bezpośrednio w tabeli, ale gdy zachodzi potrzeba bez oporów dodam kolejną tabelę.
2) mamy jedną, nieustannie (siłą rzeczy) rozwijaną "instancję" biblioteki i każdy projekt ma do niej referencje (przez externals w svn czy submodules w gicie); ograniczając się jedynie do "przyrostowego" rozwijania biblioteki tego typu może być średnio wygodne, ponieważ w takich kawałkach kodu często mogą nastąpić zmiany (ciągle da się wymyślać lepsze sposoby na rozwiązanie częstych problemów); natomiast modyfikacja już napisanego kodu może zadziałać w jednym projekcie, ale popsuć wszystkie inne… w ten sposób nawet nasze wewnętrzne API musimy traktować jako de facto udostępniony na zewnątrz framework, a taki krok chyba najlepiej odsunąć w czasie tak daleko jak to możliwe
Właśnie taki framework, w zasadzie jedyny problem w nim to zależność od frameworka od MS.
Da się to spokojnie utrzymać przy użyciu svna. Rozwijane są tylko wersje zależne od .NET 2.0 i 3.5.
Co do psucia innych projektów, to raczej kwestia wychowania programistów i audytu kodu takiego frameworka.
To ja się podzielę implementacją ToEnum którą ja akurat wykorzystuje:
To co lepsze? "where T : IComparable, IFormattable" czy "where TEnum : struct"
@Jacek
lepsze jest struct, ze wzgledu na to ze Enum nie moze byc klasa – jest typu value type i w systemie traktowany jest jako struktura (sa tylko dwie kategorie strutkrualne, enum i struct). Twoje rozwiaznie zezwala na przekazywanei obiektow – nie zaleznie jakie bys dodal tam interfejsy.
Przyklad:
public class Test : IComparable, IFormattable
{
public int CompareTo(object obj)
{
return 0;
}
public string ToString(string format, IFormatProvider formatProvider)
{
return base.ToString();
}
}
u Ciebie wywali to blad, u Procenta bedzie blad kompilacji – imo, lepsze rozwiazanie. Jedynym przypadkiem kiedy u Procenta moze pojawic sie blad to wtedy kiedy przekaze strukture ktora nie jest enum np.: int. Kompilator wtedy tego nie wykryje.
dodatkowo przechwytywanie bledu w takiej metodzie – nie jest dobrym pomyslem. lepiej by bylo napisac TryToEnum zwracajace bool i outowy enum. Lub poprostu wyrzucic wyjatek, ktory powinien zostac albo gdzies dalej zaalogwany albo wyrzucony uzytkownikowi (w zaleznosci od zapotrzebowania).
dodatkowo moze ty musiales miec w swoim rozwiazaniu wartosc domyslna zwracana, ja bym tego u siebie nie chcial, w szczegolnosci kiedy parsuje enum i to jeszcze na przyklad pobrany z bazy danych. Oznaczaloby to, ze ktos mi namieszal w danych w bazie i to nie jest dobry sygnal. Ale rozne sa wymagania wiec tez rozumiem ze takie cos moze byc przydatne, ale nalezy na to uwarzac :)
Gutek
@Procent
"Moim zdaniem nie ma "złotej reguły". Jeśli taki enum miałby być mapowany na najprostszą tabelę słownikową (id+nazwa) z kilkoma zaledwie elementami (np. stan zamówienia) to nie ma sensu tworzyć osobnej tabeli."
Możesz mieć jedną tabelę słownik i trzymać w niej takie rzeczy. Możesz przy zapisie do bazy rzutować na byte, przy odczycie na MyEnum. Pewnie można wymyśleć jeszcze wiele innych rozwiązań. W Twoim przypadku zmiana nazwy elementu enuma już zaczyna być problemem. A nigdy tak nie zrobiłem z powodów opisanych przez Dario-G.
Co do bibliotek, to przez większą część zawodowej kariery spotykałem się z (1) rozwiązaniem. Obecnie pracuję z (2), tylko nie externals w svn, a aktualizacja wersji biblioteki w projekcie następuje przy świadomym działaniu developera. Oddzielenie części kodu, który nie każdy może zmieniać i każda zmiana jest bardziej przemyślana niż zwykły commit jest wg mnie OK. I raczej w tej bibliotece nie ma takich drobiazgów jak pokazałeś.
A kod rzeczywiście zgrabny.
Artur