2011-10-10 13 views
5

Było wiele pytań dotyczących obsługi typów odniesienia nieululujących w .NET. Wielką nadzieją były umowy kodowe, ale ogranicza się to do sprawdzania czasu pracy dla tych, którzy mają ograniczony budżet.Nielegalne typy odniesienia (jeszcze raz)

Co do podejść innych niż umowy kodowe, Jon Skeet napisał o tym kilka lat temu blog post, a jeden z komentatorów dostarczył przydatnego wyglądu NonNull struct, który zmodyfikował IL, aby wyłączyć domyślny konstruktor. Wydaje się, że jest to doskonałe podejście i mogę sobie wyobrazić rozszerzenie go tak, aby zapewniał wszelkiego rodzaju nie-nullable microtypes. Manipulacja IL może być etapem następującym po uruchomieniu wywołanym przez atrybut na struct, np.

//Microtype representing a non-zero age, so we want to disable the default ctor 
[NoDefaultConstructor] 
public struct Age 
{ 
    public Age(int age) 
    { 
     // Implementation (including validation) elided 
    } 
} 

Zanim przejdę do dalszych rozważań, chciałbym zapytać, czy ktoś wie o jakichkolwiek problemach z tym związanych? Nie byłem w stanie wymyślić żadnego.

+0

Jaki jest Twój przypadek użycia w przypadku niezerowego typu odniesienia? –

+1

Dla tych, których budżet rozciąga się na ReSharper, jest tam przydatna funkcja sprawdzania nieważności (choć oczywiście nie jest tak kompletna jak rzeczy kontraktów w drogich wersjach VS). – AakashM

+0

@AnthonyPegram Myślę, że większość zastosowań typów referencyjnych niejawnie nie ma wartości NULL , więc egzekwowanie, że za pomocą metody podpisu jest wygraną pod względem dokumentacji i bezpieczeństwa. – Akash

Odpowiedz

6

Można to łatwo pokonać - czas wykonywania nie próbuje wywołać konstruktora bez parametrów struktury (jeśli jest obecny) w każdym scenariuszu.

W szczególności nie zostanie wywołany podczas tworzenia tablicy typu struct.

Age[] ages = new Age[3]; 

// This guy skips your "real" ctor as well as the "invalid" parameterless one. 
Age age = ages[0]; 

... lub default(structType) wyrażenia:

// Invalid state here too. 
Age age = default(Age); 

Od Jon Skeet na empirical research do tych rzeczy, oto lista innych operacji, które nie wywołać konstruktor:

  • Po prostu deklarowanie zmiennej, lokalnej, statycznej lub instancji
  • Boks
  • Korzystanie default(T) w ogólnej metody
  • Korzystanie new T() w ogólnej metody

teraz sytuacja, która pozostawia cię w to, że trzeba jakoś przetestować dla każdego Age przykład, czy instancja został stworzony przez obejście ogrodzenia - co nie jest dużo lepsze niż brak wzniesienia ogrodzenia w pierwszej kolejności.

+0

Dzięki Ani, przegapiłem artykuł uzupełniający Jona. Chociaż są to wszystkie ważne obawy, nadal zastanawiam się, czy to podejście zasługuje, zwłaszcza na kod wewnętrzny, np. NonNull w metodzie podpis jest jasną dokumentacją (z * pewnym * egzekwowaniem * kompilatora) co do dopuszczalnych wartości dla argumentu. Czuję się dobrze z niedoskonałym rozwiązaniem, jeśli niedoskonałości są ujawniane tylko przez złośliwych użytkowników (moi koledzy to zawodowcy) lub przez rzadkie wypadki. Jednak nie zdecydowałem, czy twoje przykłady należą do kategorii "rzadkich wypadków"! – Akash

+0

@Akash: Trudno byłoby zaklasyfikować tablicę struktur lub "default (T)" w ogólnej metodzie, która prawdopodobnie będzie "złośliwa"/"rzadki wypadek". Kiedy to zaakceptujemy, oczywiste jest, że będziemy musieli potwierdzić każde wystąpienie - co pozostawia nas z miejsca, z którego zaczęliśmy. :) – Ani

+0

Ale jeśli ograniczamy dyskusję do mikroprocesów, nie widzę powodu, dla którego ktokolwiek miałby utworzyć instancję za pomocą ogólnej metody fabrycznej (GetMicroType ). Podobnie, zamiast przechodzenia Age [] na metodę, przechodzisz w AgeCollection, który sam będzie miał walidację na swoich argumenach ctor. Walczę o to, czego mi brakuje, ale biorąc pod uwagę, że nie znam nikogo używającego tego podejścia, uważam, że muszą istnieć praktyczne problemy, a nie tylko kwestie teoretyczne. – Akash

Powiązane problemy