fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
< 1 minut

String.ToEnum<>


03.02.2010

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>()
0 0 votes
Article Rating
12 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Szymon Pobiega
14 years ago

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
14 years ago

@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
emdzej
14 years ago

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

Assassin
Assassin
14 years ago

@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
14 years ago

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
14 years ago

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
14 years ago

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
14 years ago

@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
PiotrB
14 years ago

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
14 years ago

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
14 years ago

@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
Artur
14 years ago

@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

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również