. jak .NET

Blog by Maciej "Procent" Aniserowicz

String.ToEnum<>

3 lutego 2010 06:53 w kategorii: pro

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>()

Komentarze

dotnetomaniak.pl

3 lutego 2010 01:23

Trackback from dotnetomaniak.pl

Maciej Aniserowicz | String.ToEnum<>

Szymon Pobiega

3 lutego 2010 07:42

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?

procent

3 lutego 2010 07:51

@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ść.

emdzej

3 lutego 2010 09:32

Mały i elegancki helper ;) Bardzo lubie takie "zgrabne" rozwiązania!

Assassin

3 lutego 2010 10:58

@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.

procent

3 lutego 2010 11:20

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.

Dawid Kowalski

3 lutego 2010 15:08

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 ?

dario-g

3 lutego 2010 22:17

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.

procent

3 lutego 2010 22:21

@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ę.

PiotrB

3 lutego 2010 23:00

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.

Jacek Ciereszko

3 lutego 2010 23:53

To ja się podzielę implementacją ToEnum którą ja akurat wykorzystuje:


[DebuggerStepThrough]
public static T ToEnum<T>(this string target, T defaultValue) where T : IComparable, IFormattable
{
T convertedValue = defaultValue;

if (!string.IsNullOrEmpty(target))
{
try
{
convertedValue = (T) Enum.Parse(typeof(T), target.Trim(), true);
}
catch (ArgumentException)
{
// jakas tam obsluga
}
}

return convertedValue;
}


To co lepsze? "where T : IComparable, IFormattable" czy "where TEnum : struct"

Gutek

4 lutego 2010 02:29

@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

Artur

5 lutego 2010 10:58

@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

Dodaj komentarz


 

[b][/b] - [i][/i] - [u][/u] - [quote][/quote] - [code][/code]