Występują tutaj dwa problemy: 1) testowanie, czy typ jest zerowalny; oraz 2) testowanie, czy obiekt reprezentuje typ zerowalny.
W przypadku numeru 1 (testowanie typu) oto rozwiązanie, którego użyłem we własnych systemach: TypeIsNullable-check solution
W przypadku problemu 2 (testowanie obiektu) powyższe rozwiązanie Deana Chalk'a działa dla typów wartości, ale nie działa dla typów referencyjnych, ponieważ użycie przeciążenia <T> zawsze zwraca false. Ponieważ typy referencyjne są z natury zerowalne, testowanie typu referencyjnego zawsze powinno zwracać wartość true. Objaśnienia dotyczące tej semantyki znajdują się w nocie [O „zerowalności”] poniżej. Oto moja modyfikacja podejścia Deana:
public static bool IsObjectNullable<T>(T obj)
{
// If the parameter-Type is a reference type, or if the parameter is null, then the object is always nullable
if (!typeof(T).IsValueType || obj == null)
return true;
// Since the object passed is a ValueType, and it is not null, it cannot be a nullable object
return false;
}
public static bool IsObjectNullable<T>(T? obj) where T : struct
{
// Always return true, since the object-type passed is guaranteed by the compiler to always be nullable
return true;
}
A oto moja modyfikacja kodu testu klienta dla powyższego rozwiązania:
int a = 123;
int? b = null;
object c = new object();
object d = null;
int? e = 456;
var f = (int?)789;
string g = "something";
bool isnullable = IsObjectNullable(a); // false
isnullable = IsObjectNullable(b); // true
isnullable = IsObjectNullable(c); // true
isnullable = IsObjectNullable(d); // true
isnullable = IsObjectNullable(e); // true
isnullable = IsObjectNullable(f); // true
isnullable = IsObjectNullable(g); // true
Powodem, dla którego zmodyfikowałem podejście Deana w IsObjectNullable <T> (T t) jest to, że jego oryginalne podejście zawsze zwracało wartość false dla typu odniesienia. Ponieważ metoda taka jak IsObjectNullable powinna być w stanie obsłużyć wartości typu odwołania, a ponieważ wszystkie typy odwołań są z natury zerowalne, wówczas jeśli zostanie przekazany typ odwołania lub wartość null, metoda powinna zawsze zwracać wartość true.
Powyższe dwie metody można zastąpić następującą pojedynczą metodą i osiągnąć ten sam wynik:
public static bool IsObjectNullable<T>(T obj)
{
Type argType = typeof(T);
if (!argType.IsValueType || obj == null)
return true;
return argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(Nullable<>);
}
Jednak problem z tym ostatnim podejściem opartym na jednej metodzie polega na tym, że wydajność spada, gdy używany jest parametr Nullable <T>. Wykonanie ostatniego wiersza tej pojedynczej metody zajmuje znacznie więcej czasu, niż pozwala kompilatorowi na wybranie przeciążenia drugiej metody pokazanego wcześniej, gdy w wywołaniu IsObjectNullable użyto parametru Nullable <T>. Dlatego optymalnym rozwiązaniem jest zastosowanie przedstawionego tutaj podejścia opartego na dwóch metodach.
CAVEAT: Ta metoda działa niezawodnie tylko wtedy, gdy zostanie wywołana przy użyciu oryginalnego odwołania do obiektu lub dokładnej kopii, jak pokazano w przykładach. Jednak jeśli obiekt zerowalny jest umieszczany w pudełku z innym typem (takim jak obiekt itp.), Zamiast pozostać w oryginalnej formie zerowalności <>, ta metoda nie będzie działać niezawodnie. Jeśli kod wywołujący tę metodę nie używa oryginalnego odwołania do rozpakowanego obiektu lub dokładnej kopii, nie może wiarygodnie określić nullabności obiektu za pomocą tej metody.
W większości scenariuszy kodowania, aby określić nullability, należy zamiast tego polegać na testowaniu typu oryginalnego obiektu, a nie jego odwołania (np. Kod musi mieć dostęp do oryginalnego typu obiektu w celu ustalenia nullability). W tych bardziej powszechnych przypadkach IsTypeNullable (patrz link) jest niezawodną metodą określania wartości dopuszczalnej.
PS - O „zerowalności”
Powinienem powtórzyć wypowiedź na temat nullabii, którą wypowiedziałem w osobnym poście, która odnosi się bezpośrednio do właściwego rozwiązania tego tematu. To znaczy, uważam, że przedmiotem dyskusji nie powinno być sprawdzenie, czy obiekt jest ogólnym typem Nullable, ale raczej to, czy można przypisać wartość null do obiektu tego typu. Innymi słowy, myślę, że powinniśmy ustalić, czy typ obiektu jest zerowalny, a nie czy jest zerowalny. Różnica polega na semantyce, a mianowicie na praktycznych powodach określania zerowalności, co zwykle jest najważniejsze.
W systemie używającym obiektów o typach, które mogą być nieznane do czasu wykonania (usługi sieciowe, wywołania zdalne, bazy danych, kanały itp.), Powszechnym wymogiem jest ustalenie, czy do obiektu można przypisać wartość zerową, czy też obiekt może zawierać zero. Wykonywanie takich operacji na typach nie dopuszczających wartości zerowej prawdopodobnie spowoduje błędy, zwykle wyjątki, które są bardzo drogie zarówno pod względem wydajności, jak i wymagań kodowania. Aby przyjąć wysoce preferowane podejście proaktywnego unikania takich problemów, konieczne jest ustalenie, czy obiekt dowolnego typu może zawierać wartość zerową; tzn. czy jest to ogólnie „zerowalne”.
W bardzo praktycznym i typowym sensie, dopuszczalność zerowania w kategoriach .NET wcale nie musi oznaczać, że typ obiektu jest formą dopuszczenia wartości zerowej. W wielu przypadkach obiekty mają typy referencyjne, mogą zawierać wartość zerową, a zatem wszystkie mają wartości zerowe; żadne z nich nie ma typu Nullable. Dlatego, dla praktycznych celów w większości scenariuszy, należy przeprowadzić testowanie ogólnej koncepcji nullability w porównaniu z zależną od implementacji koncepcją Nullable. Dlatego nie powinniśmy się rozłączać, skupiając się wyłącznie na typie .NET Nullable, ale raczej wcielając nasze rozumienie jego wymagań i zachowania w proces koncentrowania się na ogólnej, praktycznej koncepcji nullability.