Jak sprawdzić, czy obiekt jest zerowalny?

202

Jak sprawdzić, czy dany obiekt jest zerowy, innymi słowy, jak zaimplementować następującą metodę ...

bool IsNullableValueType(object o)
{
    ...
}

EDYCJA: Szukam typów dopuszczających wartości zerowe. Nie miałem na myśli typów referencyjnych.

//Note: This is just a sample. The code has been simplified 
//to fit in a post.

public class BoolContainer
{
    bool? myBool = true;
}

var bc = new BoolContainer();

const BindingFlags bindingFlags = BindingFlags.Public
                        | BindingFlags.NonPublic
                        | BindingFlags.Instance
                        ;


object obj;
object o = (object)bc;

foreach (var fieldInfo in o.GetType().GetFields(bindingFlags))
{
    obj = (object)fieldInfo.GetValue(o);
}

obj odnosi się teraz do obiektu typu bool( System.Boolean) o wartości równej true. To, czego naprawdę chciałem, to obiekt typuNullable<bool>

Więc teraz, jako obejście, postanowiłem sprawdzić, czy o jest zerowalne, i utworzyć opakowanie zerowalne wokół obj.

Samouk
źródło
Czy kod powinien zawierać ciągi znaków jako wartości zerowe? Są to nietypowe ValueType, które wydają się być zerowalne. Czy nie są to ValueType?
TamusJRoyce
Łańcuch nie jest typem wartości. Jest to typ odniesienia.
Suncat2000
To naprawdę dobre pytanie! „Type.IsNullableType ()” jest swego rodzaju myleniem, ponieważ w rzeczywistości sprawdza tylko, czy typ jest „Nullable <T>”, który nie zwrócił oczekiwanych wyników, jeśli rzeczywiście chciałbyś sprawdzić, czy nie ma typów, które mogą zaakceptować wartość null wartość (np. próbowałem użyć z a.IsNullableType (), gdzie „a” był „typof (ciąg)” określony w czasie wykonywania)
ErrCode
Odpowiedź znajduje się w fieldInfo.FieldType: sprawdź, czy FieldType jest ogólny, a ogólny rodzaj jest typu Nullable <>. (Przykład: if (FieldType.IsGenericType && FieldType.GetGenericTypeDefinition () == typeof (Nullable <>))). Nie próbuj uzyskać obiektu obj.GetType (), ponieważ będzie miał UndelyingSystemType zmiennej Nullable <T> T (w twoim przypadku typu boolowskiego zamiast Nullable <Boolean>), jest to problem z boksem.
SoLaR,

Odpowiedzi:

271

Istnieją dwa typy zerowalne - Nullable<T>i typ odniesienia.

Jon poprawił mnie, że trudno jest wpisać tekst, jeśli jest w ramce, ale możesz to zrobić za pomocą ogólnych: - a co powiesz na to poniżej. W rzeczywistości jest to typ testowy T, ale użycie objparametru wyłącznie do wnioskowania typu ogólnego (aby ułatwić wywoływanie) - działałoby prawie identycznie bez objparametrów.

static bool IsNullable<T>(T obj)
{
    if (obj == null) return true; // obvious
    Type type = typeof(T);
    if (!type.IsValueType) return true; // ref-type
    if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
    return false; // value-type
}

Ale to nie zadziała tak dobrze, jeśli wartość została już zapakowana w zmienną obiektową.

Dokumentacja Microsoft: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/nullable-types/how-to-identify-a-nullable-type

Marc Gravell
źródło
7
Ostatnia linia jest ważna tylko wtedy, gdy jakoś uda ci się uzyskać w pudełku Nullable <T> zamiast boksu prosto do T. Jest to możliwe, ale trudne do osiągnięcia z tego, co pamiętam.
Jon Skeet,
Ten kod był dla mnie pomocny, nie dlatego, że dostałem pudełko Nullable <T>, ale ponieważ pisałem ogólną klasę bazową konwertera WPF, a niektóre właściwości są nullable, więc użyłem Nullable.GetUnderlyingType do wykrycia tej sprawy i Activator.CreateInstance, aby zrobić pudełko nullable, (Convert.ChangeType nie obsługuje nullables btw).
Qwertie,
1
@Abel, jeśli masz na myśli, że to jego edycja w celu wyjaśnienia, że ​​nie rozważał typów referencji, myślę, że moja odpowiedź była wcześniejsza niż ta edycja; podejrzewam, że czytelnik może tam podjąć własną decyzję, w oparciu o własne potrzeby (potwierdzone: jego komentarz do ref-typów dodany o 14:42; moja odpowiedź brzmiała <= 14:34)
Marc Gravell
1
Czy (obj == null) zgłosi wyjątek, gdy obj = 1?
Qi Fan
3
@JustinMorgan Jeśli Tparametr ogólny jest ograniczony T : struct, Tto nie może być Nullable<>, więc nie musisz w tym przypadku sprawdzać! Wiem, że typ Nullable<>jest strukturą, ale w języku C # ograniczenie where T : structwyraźnie wyklucza typy wartości dopuszczających wartości zerowe. Specyfikacja mówi: „Zauważ, że chociaż sklasyfikowany jako typ wartości, typ dopuszczający wartości zerowe (§ 4.1.1) nie spełnia ograniczenia typu wartości”.
Jeppe Stig Nielsen,
46

Istnieje bardzo proste rozwiązanie wykorzystujące przeciążenia metod

http://deanchalk.com/is-it-nullable/

fragment:

public static class ValueTypeHelper
{
    public static bool IsNullable<T>(T t) { return false; }
    public static bool IsNullable<T>(T? t) where T : struct { return true; }
}

następnie

static void Main(string[] args)
{
    int a = 123;
    int? b = null;
    object c = new object();
    object d = null;
    int? e = 456;
    var f = (int?)789;
    bool result1 = ValueTypeHelper.IsNullable(a); // false
    bool result2 = ValueTypeHelper.IsNullable(b); // true
    bool result3 = ValueTypeHelper.IsNullable(c); // false
    bool result4 = ValueTypeHelper.IsNullable(d); // false
    bool result5 = ValueTypeHelper.IsNullable(e); // true
    bool result6 = ValueTypeHelper.IsNullable(f); // true
Dean Chalk
źródło
7
plus jeden dla pana za dodanie przypadków testowych. Użyłem tych przypadków testowych do sprawdzenia wszystkich innych odpowiedzi. Więcej osób powinno przejść ten dodatkowy kawałek.
Marty Neal
4
Jeśli chodzi o to, co jest warte, nie działa to w VB.NET. Powoduje to błąd kompilatora „Rozwiązanie problemu z przeciążeniem nie powiodło się, ponieważ brak dostępnego parametru„ IsNullable ”jest najbardziej specyficzny dla tych argumentów ” we wszystkich sytuacjach, Truektóre zostaną zwrócone.
ckittel
1
Naprawdę podoba mi się to rozwiązanie - szkoda, że ​​VB nie może sobie z tym poradzić. Próbowałem pracować wokół z ValueType ale wpadł kłopoty z VB kompilatora niekonsekwentna o których używają do przeciążenia na podstawie tego, czy została ona wywołana jako wspólnej metody lub rozszerzenia, nawet podniesione pytanie o to, jak się wydaje dziwne: stackoverflow.com/ pytania / 12319591 /…
James Close
22
Sprawdzasz typ czasu kompilacji , ale jest już oczywiste (z intellisense), czy typ czasu kompilacji jest zerowy ( System.Nullable<>). Jeśli powiesz, object g = e;a następnie ValueTypeHelper.IsNullable(g), czego można się spodziewać, aby uzyskać?
Jeppe Stig Nielsen,
18
Właśnie zweryfikowałem; to nie działa , jak powiedział Jeppe. Jeśli zmienne zostaną rzutowane na obiekt, zawsze zwróci false. W ten sposób nie można w ten sposób określić typu nieznanego obiektu w środowisku wykonawczym. Działa to tylko wtedy, gdy typ jest ustalony w czasie kompilacji, aw takim przypadku wcale nie potrzebujesz sprawdzania czasu wykonywania.
HugoRune
30

Pytanie „Jak sprawdzić, czy typ jest zerowalny?” jest rzeczywiście „Jak sprawdzić, czy typ jest Nullable<>?”, które można uogólnić do „Jak sprawdzić, czy typ jest skonstruowany rodzaj jakiegoś rodzajowego typu?”, tak, że nie tylko odpowiada na pytanie „Czy ?” ale także „Czy ?”.Nullable<int>Nullable<>List<int>List<>

Większość dostarczonego rozwiązania korzysta z Nullable.GetUnderlyingType()metody, która oczywiście będzie działać tylko w przypadku Nullable<>. Nie widziałem ogólnego rozwiązania odblaskowego, które działałoby z jakimkolwiek typem ogólnym, więc postanowiłem dodać go tutaj dla potomności, mimo że już na to pytanie już dawno odpowiedziano.

Aby sprawdzić, czy typ jest jakaś forma Nullable<>użyciu odbicia, trzeba najpierw przekonwertować typu rodzajowego skonstruowany, na przykład Nullable<int>, do ogólnej definicji typu, Nullable<>. Możesz to zrobić za pomocą GetGenericTypeDefinition()metody Typeklasy. Następnie możesz porównać wynikowy typ z Nullable<>:

Type typeToTest = typeof(Nullable<int>);
bool isNullable = typeToTest.GetGenericTypeDefinition() == typeof(Nullable<>);
// isNullable == true

To samo można zastosować do dowolnego rodzaju ogólnego:

Type typeToTest = typeof(List<int>);
bool isList = typeToTest.GetGenericTypeDefinition() == typeof(List<>);
// isList == true

Kilka typów może wydawać się takich samych, ale inna liczba argumentów typu oznacza, że ​​jest to zupełnie inny typ.

Type typeToTest = typeof(Action<DateTime, float>);
bool isAction1 = typeToTest.GetGenericTypeDefinition() == typeof(Action<>);
bool isAction2 = typeToTest.GetGenericTypeDefinition() == typeof(Action<,>);
bool isAction3 = typeToTest.GetGenericTypeDefinition() == typeof(Action<,,>);
// isAction1 == false
// isAction2 == true
// isAction3 == false

Ponieważ Typeobiekty są tworzone instancji raz dla każdego typu, możesz sprawdzić równość odniesienia między nimi. Więc jeśli chcesz sprawdzić, czy dwa obiekty mają tę samą definicję typu ogólnego, możesz napisać:

var listOfInts = new List<int>();
var listOfStrings = new List<string>();

bool areSameGenericType =
    listOfInts.GetType().GetGenericTypeDefinition() ==
    listOfStrings.GetType().GetGenericTypeDefinition();
// areSameGenericType == true

Jeśli chcesz sprawdzić, czy obiekt jest zerowalny, a nie a Type, możesz użyć powyższej techniki razem z rozwiązaniem Marc Gravell, aby utworzyć dość prostą metodę:

static bool IsNullable<T>(T obj)
{
    if (!typeof(T).IsGenericType)
        return false;

    return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>);
}
Allon Guralnek
źródło
@ AllonGuralnek W mojej odpowiedzi znajduje się wersja uproszczona. Chciałem to zrobić jako edytuj, a ponieważ moja reputacja nie jest na twoim poziomie, byłaby edytowana bez mojego imienia na twojej odpowiedzi, mimo to wydaje się, że recenzja zawsze strzela mi w nogę, że adresuje autora, nawet jeśli był nie. Dziwny świat, niektórzy ludzie nie dostają definicji :).
ipavlu,
@ipavlu: Twoja wersja nie jest uproszczona, w rzeczywistości jest bardziej skomplikowana. Myślę, że masz na myśli, że jest zoptymalizowany, ponieważ buforujesz wynik. To utrudnia zrozumienie.
Allon Guralnek
@ AllonGuralnek statyczna klasa ogólna i statyczne pola inicjowane jednorazowo, co jest skomplikowane? Dobry Boże, popełniłem straszną zbrodnię :).
ipavlu
@ipavku: Tak, ponieważ nie ma to nic wspólnego z pytaniem „Jak sprawdzić, czy obiekt ma wartość zerową?”. Staram się, aby było to proste i rzeczowe, i unikam wprowadzania niepotrzebnych i niepowiązanych koncepcji.
Allon Guralnek
1
@nawfal: Jeśli dobrze cię zrozumiałem, wypytujesz moją implementację w obliczu istnienia Nullable.GetUnderlyingType()już dostarczonego przez framework. Dlaczego nie skorzystać z tej metody w ramach? Cóż, powinieneś. Jest jaśniejszy, bardziej zwięzły i lepiej przetestowany. Ale w moim poście staram się nauczyć, jak używać refleksji, aby uzyskać potrzebne informacje, aby ktoś mógł zastosować je do dowolnego typu (zamieniając na typeof(Nullable<>)inny typ). Jeśli spojrzysz na źródła GetUnderlyingType()(oryginalne lub zdekompilowane), zobaczysz, że jest bardzo podobny do mojego kodu.
Allon Guralnek
30

To działa dla mnie i wydaje się proste:

static bool IsNullable<T>(T obj)
{
    return default(T) == null;
}

Dla typów wartości:

static bool IsNullableValueType<T>(T obj)
{
    return default(T) == null && typeof(T).BaseType != null && "ValueType".Equals(typeof(T).BaseType.Name);
}
Erik
źródło
7
O ile jest to warte, jest to również test używany przez Microsoft
kanton7
1
Fajnie ... Czy to nie najlepsza odpowiedź, bo przyszła później? Najlepsza odpowiedź jest dla mnie myląca.
Vincent Buscarello
1
To powinna być najlepsza odpowiedź. Po wielu dniach próbowania różnych metod losowo pomyślałem o tym rozwiązaniu, wypróbowałem je i wydaje się, że działa idealnie (w porównaniu do najlepiej ocenianej odpowiedzi)
user3163495,
2
Jest to doskonałe rozwiązanie, aby dowiedzieć się, czy dowolną instancję można ustawić na NULL, ale zwróci wartość true dla wszystkiego , co można ustawić na zero, w tym dla zwykłych obiektów. Ważne jest, aby zdać sobie sprawę, że pierwotne pytanie szczególnie chciało wykryć Nullable ValueTypes.
JamesHoux
20

Cóż, możesz użyć:

return !(o is ValueType);

... ale sam obiekt nie ma wartości zerowej ani żadnej innej - typ jest. Jak planowałeś to wykorzystać?

Jon Skeet
źródło
2
To mnie trochę odtrąciło. np. int? i = 5; typeof (i) zwraca System.Int32 zamiast Nullable <Int32> - typeof (int?) zwraca Nullable <Int32> .. gdzie mogę uzyskać wyjaśnienie na ten temat?
Gishu,
2
typeof (i) spowoduje błąd kompilatora - nie można używać typeof ze zmienną. Co właściwie zrobiłeś?
Jon Skeet
15
i.GetType () najpierw wyświetli pole do obiektu, i nie ma czegoś takiego jak boksowany typ zerowalny - Nullable <int> zostaje zapakowany w odwołanie zerowe lub w pole int.
Jon Skeet
Ten sposób jest lepszy niż Nullable.GetUnderlyingType (type)! = Null?
Kiquenet
@Kiquenet: Nie mamy tutaj typu - tylko wartość.
Jon Skeet
11

Najprostszy sposób, w jaki mogę to ustalić:

public bool IsNullable(object obj)
{
    Type t = obj.GetType();
    return t.IsGenericType 
        && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
CARLOS LOTH
źródło
+1. Doskonałe rozwiązanie dla pudełkowanych typów zerowanych. Nie przetestowałem tego jeszcze specjalnie. Więc jeśli ktokolwiek może to zweryfikować, byłoby to mile widziane.
TamusJRoyce
Już to przetestowałem. Musiałem stworzyć coś w Nullablerodzaju, ale z inną semantyką. W mojej sytuacji powinienem wspierać nulljako prawidłową wartość, a także nie wspierać żadnej wartości. Więc stworzył Optionaltyp. Ponieważ obsługa nullwartości była konieczna , musiałem również zaimplementować kod do obsługi Nullablewartości w ramach mojej implementacji. Stąd pochodzi ten kod.
CARLOS LOTH
9
Myślę, że to rozwiązanie jest złe. Przekazanie typu Nullable wartości jako argumentu do metody oczekującej parametru obiektu typu powinno spowodować wystąpienie boksu. Nullable jest typem wartości, a wynikiem konwersji boksu jest typ referencyjny. Nie ma pudełkowatych nullów. Uważam, że ta metoda zawsze zwraca false?
Mishax,
1
Jakiś test na ten temat, jak inne odpowiedzi?
Kiquenet
5
Nie działa z powodu wartości boksu. Zawsze zwróci FAŁSZ.
N Rocking
10

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.

Mark Jones
źródło
8

Najprostszym rozwiązaniem, jakie wymyśliłem, jest zaimplementowanie rozwiązania Microsoft ( jak: Zidentyfikować typ zerowy (Podręcznik programowania w języku C #) ) jako metody rozszerzenia:

public static bool IsNullable(this Type type)
{
    return Nullable.GetUnderlyingType(type) != null;
}

Można to nazwać tak:

bool isNullable = typeof(int).IsNullable();

Wydaje się to również logicznym sposobem dostępu, IsNullable()ponieważ pasuje do wszystkich innych IsXxxx()metod Typeklasy.

sclarke81
źródło
1
Nie chciałeś używać „==” zamiast „! =”?
vkelman
Dobre miejsce @vkelman Zamiast dokonać tej zmiany zaktualizowałem odpowiedź, aby skorzystać z bieżącej sugestii firmy Microsoft, ponieważ zmieniła się od czasu, gdy to napisałem.
sclarke81,
6

Zachowaj ostrożność, umieszczając boks typu zerowalnego ( Nullable<int>lub na przykład int?):

int? nullValue = null;
object boxedNullValue = (object)nullValue;
Debug.Assert(boxedNullValue == null);

int? value = 10;
object boxedValue = (object)value;
Debug.Assert( boxedValue.GetType() == typeof(int))

Staje się prawdziwym typem odniesienia, więc tracisz fakt, że nie można go zerwać.

thinkbeforecoding
źródło
3

Może trochę nie na temat, ale wciąż ciekawe informacje. Znajduję wielu ludzi, którzy używają Nullable.GetUnderlyingType() != nulltożsamości, jeśli typ jest zerowalny. To oczywiście działa, ale Microsoft radzi, co następuje type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)(patrz http://msdn.microsoft.com/en-us/library/ms366789.aspx ).

Patrzyłem na to z punktu widzenia wydajności. Wniosek z poniższego testu (milion prób) jest taki, że gdy typ jest zerowalny, opcja Microsoft zapewnia najlepszą wydajność.

Nullable.GetUnderlyingType (): 1335ms (3 razy wolniej)

GetGenericTypeDefinition () == typeof (Nullable <>): 500ms

Wiem, że mówimy o krótkim czasie, ale wszyscy uwielbiają zmieniać milisekundy :-)! Więc jeśli twój szef chce, abyś skrócił kilka milisekund, to jest to twój wybawca ...

/// <summary>Method for testing the performance of several options to determine if a type is     nullable</summary>
[TestMethod]
public void IdentityNullablePerformanceTest()
{
    int attempts = 1000000;

    Type nullableType = typeof(Nullable<int>);

    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    for (int attemptIndex = 0; attemptIndex < attempts; attemptIndex++)
    {
        Assert.IsTrue(Nullable.GetUnderlyingType(nullableType) != null, "Expected to be a nullable"); 
    }

    Console.WriteLine("Nullable.GetUnderlyingType(): {0} ms", stopwatch.ElapsedMilliseconds);

    stopwatch.Restart();

    for (int attemptIndex = 0; attemptIndex < attempts; attemptIndex++)
   {
       Assert.IsTrue(nullableType.IsGenericType && nullableType.GetGenericTypeDefinition() == typeof(Nullable<>), "Expected to be a nullable");
   }

   Console.WriteLine("GetGenericTypeDefinition() == typeof(Nullable<>): {0} ms", stopwatch.ElapsedMilliseconds);
   stopwatch.Stop();
}
Roel van Megen
źródło
1
Cześć, prawdopodobnie istnieje jeden problem z pomiarem czasu, Assert może wpływać na wyniki. Czy testowałeś bez Assert? Również Console.WriteLine powinien znajdować się poza obszarem pomiaru. +1 za próbę oszacowania problemów z wydajnością :).
ipavlu
@ipavlu Console.WriteLinejest rzeczywiście poza obszarem
pomiaru
Roel, jak wspomniał ipavlu, Assertpowinien znajdować się poza pętlą. Po drugie, powinieneś również przetestować go pod kątem wartości niedopuszczalnych, aby przetestować pod kątem fałszywych przypadków. Zrobiłem podobny test (2 wartości zerowe i 4 wartości zerowe) i dostaję ~ 2 sekundy na GetUnderlyingType~ 1 sekundę GetGenericTypeDefinition, tj. GetGenericTypeDefinitionJest dwa razy szybszy (nie trzykrotnie).
nawfal
Wykonałem kolejną rundę z 2 nullabami i 2 nullabami - tym razem GetUnderlyingTypebyło 2,5 razy wolniej. Tylko z niedopuszczalnymi - tym razem zarówno szyja, jak i szyja.
nawfal
Ale co ważniejsze, GetUnderlyingTypejest przydatny, gdy musisz sprawdzić nullability i uzyskać typ podstawowy, jeśli jest nulla. Jest to bardzo przydatne i często widzisz wzory Activator.CreateInstance(Nullable.GetUnderlyingType(type) ?? type). To jest jak assłowo kluczowe, sprawdza obsadę, a także robi i zwraca wynik. Jeśli chcesz odzyskać podstawowy typ zerowalności, to GetGenericTypeDefinitionsprawdzenie, a następnie uzyskanie typu ogólnego będzie złym pomysłem. GetUnderlyingTypeJest również o wiele bardziej czytelny i niezapomniany. Nie miałbym nic przeciwko, jeśli robię to tylko ~ 1000 razy.
nawfal
0

Ta wersja:

  • wyniki buforowania są szybsze,
  • nie wymaga niepotrzebnych zmiennych, takich jak Metoda (T obj)
  • NIESKOMPLIKOWANY :),
  • tylko statyczna klasa ogólna, która ma pola obliczone jednorazowo

:

public static class IsNullable<T>
{
    private static readonly Type type = typeof(T);
    private static readonly bool is_nullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
    public static bool Result { get { return is_nullable; } }
}

bool is_nullable = IsNullable<int?>.Result;
ipavlu
źródło
Myślę, że sam odpowiedziałeś statyczną deklaracją „is_nullable”. Wskazówka: deklarować obiekty za pomocą int? (obiekt a = (int?) 8;) i zobacz, co się stanie.
SoLaR,
0

Oto, co wymyśliłem, ponieważ wszystko inne wydawało się zawieść - przynajmniej na PLC - Portable Class Library / .NET Core z> = C # 6

Rozwiązanie: Rozszerz metody statyczne dla dowolnego typu Ti Nullable<T>wykorzystaj fakt, że metoda statycznego rozszerzenia, odpowiadająca typowi bazowemu, zostanie wywołana i będzie miała pierwszeństwo przed ogólną Tmetodą rozszerzenia.

Dla T:

public static partial class ObjectExtension
{
    public static bool IsNullable<T>(this T self)
    {
        return false;
    }
}

i dla Nullable<T>

public static partial class NullableExtension
{
    public static bool IsNullable<T>(this Nullable<T> self) where T : struct
    {
        return true;
    }
}

Korzystanie z Reflection i type.IsGenericType... nie działało na moim bieżącym zestawie uruchomieniowym .NET. Dokumentacja MSDN również nie pomogła.

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {…}

Częściowo dlatego, że interfejs API Reflection został dość znacząco zmieniony w .NET Core.

Lorenz Lo Sauer
źródło
0

Myślę, że te, które używają sugerowanych testów Microsoftu, IsGenericTypesą dobre, ale w kodzie GetUnderlyingTypeMicrosoft używa dodatkowego testu, aby upewnić się, że nie przeszedłeś definicji typu ogólnego Nullable<>:

 public static bool IsNullableType(this Type nullableType) =>
    // instantiated generic type only                
    nullableType.IsGenericType &&
    !nullableType.IsGenericTypeDefinition &&
    Object.ReferenceEquals(nullableType.GetGenericTypeDefinition(), typeof(Nullable<>));
NetMage
źródło
-1

prosty sposób to zrobić:

    public static bool IsNullable(this Type type)
    {
        if (type.IsValueType) return Activator.CreateInstance(type) == null;

        return true;
    }

to są moje testy jednostkowe i wszystkie zaliczone

    IsNullable_String_ShouldReturn_True
    IsNullable_Boolean_ShouldReturn_False
    IsNullable_Enum_ShouldReturn_Fasle
    IsNullable_Nullable_ShouldReturn_True
    IsNullable_Class_ShouldReturn_True
    IsNullable_Decimal_ShouldReturn_False
    IsNullable_Byte_ShouldReturn_False
    IsNullable_KeyValuePair_ShouldReturn_False

rzeczywiste testy jednostkowe

    [TestMethod]
    public void IsNullable_String_ShouldReturn_True()
    {
        var typ = typeof(string);
        var result = typ.IsNullable();
        Assert.IsTrue(result);
    }

    [TestMethod]
    public void IsNullable_Boolean_ShouldReturn_False()
    {
        var typ = typeof(bool);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

    [TestMethod]
    public void IsNullable_Enum_ShouldReturn_Fasle()
    {
        var typ = typeof(System.GenericUriParserOptions);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

    [TestMethod]
    public void IsNullable_Nullable_ShouldReturn_True()
    {
        var typ = typeof(Nullable<bool>);
        var result = typ.IsNullable();
        Assert.IsTrue(result);
    }

    [TestMethod]
    public void IsNullable_Class_ShouldReturn_True()
    {
        var typ = typeof(TestPerson);
        var result = typ.IsNullable();
        Assert.IsTrue(result);
    }

    [TestMethod]
    public void IsNullable_Decimal_ShouldReturn_False()
    {
        var typ = typeof(decimal);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

    [TestMethod]
    public void IsNullable_Byte_ShouldReturn_False()
    {
        var typ = typeof(byte);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }

    [TestMethod]
    public void IsNullable_KeyValuePair_ShouldReturn_False()
    {
        var typ = typeof(KeyValuePair<string, string>);
        var result = typ.IsNullable();
        Assert.IsFalse(result);
    }
VJPPaz
źródło