Zastanawiałem się nad stworzeniem niestandardowych typów identyfikatorów takich jak ten:
public enum CustomerId : int { /* intentionally empty */ }
public enum OrderId : int { }
public enum ProductId : int { }
Moją podstawową motywacją do tego jest zapobieganie rodzajowi błędu, w którym przypadkowo przekazujesz orderItemId do funkcji, która oczekiwała orderItemDetailId.
Wygląda na to, że wyliczenia działają bezproblemowo ze wszystkim, czego chciałbym użyć w typowej aplikacji internetowej .NET:
- Routing MVC działa dobrze
- Serializacja JSON działa dobrze
- Każda ORM, o której mogę pomyśleć, działa dobrze
Zastanawiam się teraz: „dlaczego miałbym tego nie robić?” To jedyne wady, jakie mogę wymyślić:
- Może to mylić innych programistów
- Wprowadza niespójność w systemie, jeśli masz jakieś integralne identyfikatory.
- Może to wymagać dodatkowego rzucania
(CustomerId)42
. Ale nie sądzę, że będzie to problem, ponieważ routing ORM i MVC zazwyczaj przekazuje ci bezpośrednio wartości typu wyliczeniowego.
Więc moje pytanie brzmi: czego mi brakuje? To chyba zły pomysł, ale dlaczego?
Odpowiedzi:
To świetny pomysł, aby utworzyć typ dla każdego rodzaju identyfikatora, głównie z podanych powodów. Nie zrobiłbym tego, implementując typ jako wyliczenie, ponieważ uczynienie go odpowiednią strukturą lub klasą daje więcej opcji:
O implementacji tego możesz przeczytać w artykule Functional C #: Primitive obsesja .
źródło
Jest to sprytny hack, ale nadal hack, który nadużywa funkcji czegoś, co nie jest zamierzonym celem. Hacki mają koszt utrzymania w utrzymaniu, więc nie wystarczy, że nie możesz znaleźć żadnych innych wad, musi także mieć znaczące korzyści w porównaniu z bardziej idiomatycznym sposobem rozwiązania tego samego problemu.
Idiomatycznym sposobem byłoby utworzenie typu opakowania lub struktury z jednym polem. Zapewni to bezpieczeństwo tego samego typu w bardziej wyraźny i idiomatyczny sposób. Chociaż twoja propozycja jest wprawdzie sprytna, nie widzę żadnych znaczących korzyści z używania typów opakowań.
źródło
Z mojego POV pomysł jest dobry. Intencja nadania różnych typów niepowiązanym klasom identyfikatorów, a w inny sposób wyrażenia semantyki za pomocą typów i umożliwienia kompilatorowi sprawdzenia, jest dobra.
Nie wiem, jak to działa pod kątem wydajności .NET, np. Czy wartości wyliczenia są koniecznie zapakowane. Kod OTOH, który działa z bazą danych, prawdopodobnie mimo wszystko jest powiązany z We / Wy, a identyfikatory w pudełkach nie będą stanowić żadnego wąskiego gardła.
W idealnym świecie identyfikatory byłyby niezmienne i porównywalne jedynie pod względem równości; rzeczywiste bajty byłyby szczegółami implementacji. Niepowiązane identyfikatory miałyby niezgodne typy, więc przez pomyłkę nie można było użyć jednego dla drugiego. Twoje rozwiązanie praktycznie pasuje do rachunku.
źródło
Nie możesz tego zrobić, wyliczenia muszą być predefiniowane. Więc jeśli nie chcesz wstępnie zdefiniować całego spektrum możliwych wartości identyfikatorów klientów, Twój typ będzie bezużyteczny.
Jeśli użyjesz, przeciwstawisz się swojemu głównemu celowi, jakim jest zapobieganie przypadkowemu przypisaniu identyfikatora typu A do identyfikatora typu B. Tak więc wyliczenia nie są pomocne w twoim celu.
Pojawia się jednak pytanie, czy pomocne byłoby utworzenie typów dla identyfikatora klienta, identyfikatora zamówienia i identyfikatora produktu poprzez zejście z Int32 (lub Guid). Mogę wymyślić powody, dla których byłoby to niewłaściwe.
Celem identyfikatora jest identyfikacja. Typy integralne są już do tego idealne, nie można ich już zidentyfikować po zejściu z nich. Specjalizacja nie jest wymagana ani pożądana, twój świat nie będzie reprezentowany lepiej dzięki różnym typom identyfikatorów. Więc z perspektywy OO byłoby źle.
Z twoją sugestią chcesz czegoś więcej niż bezpieczeństwa typu, chcesz cenić bezpieczeństwo i to jest poza dziedziną modelowania, to jest problem operacyjny.
źródło
public ActionResult GetCustomer(CustomerId custId)
rzutowania nie jest potrzebne - framework MVC dostarczy wartość.