Sprawdzanie, czy obiekt jest liczbą w C #

91

Chciałbym sprawdzić, czy obiekt jest tak wiele, że .ToString()spowodowałoby ciąg zawierający cyfry i +, -,.

Czy jest to możliwe przez proste sprawdzanie typów w .net (np.:) if (p is Number)?

Czy powinienem przekonwertować na ciąg, a następnie spróbować przeanalizować podwojenie?

Aktualizacja: Aby wyjaśnić, że mój obiekt to int, uint, float, double i tak dalej, nie jest to ciąg. Próbuję utworzyć funkcję, która serializuje dowolny obiekt do XML w następujący sposób:

<string>content</string>

lub

<numeric>123.3</numeric>

lub zgłoś wyjątek.

Piotr Czapla
źródło
5
Wygląda na to, że próbujesz napisać własny XmlSerializer - co jest nie tak z jednym dostawcą przez .NET- msdn.microsoft.com/en-us/library/… ?
RichardOD,
2
Możesz być w stanie obejść ten problem, definiując format XML za pomocą XSD, a następnie tworząc obiekt, do którego możesz serializować dane za pomocą dostarczonego narzędzia XSD - msdn.microsoft.com/en-us/library/x6c1kb0s % 28VS.71% 29.aspx
Dexter
@RichardOD: Czy mogę użyć serializacji XML do serializacji obiektu []? Potrzebuję go, aby wywołać funkcję Flash adobe.com/livedocs/flex/201/html/wwhelp/wwhimpl/common/html/…
Piotr Czapla.

Odpowiedzi:

183

Będziesz musiał po prostu sprawdzić typ dla każdego z podstawowych typów liczbowych.

Oto metoda rozszerzenia, która powinna wykonać zadanie:

public static bool IsNumber(this object value)
{
    return value is sbyte
            || value is byte
            || value is short
            || value is ushort
            || value is int
            || value is uint
            || value is long
            || value is ulong
            || value is float
            || value is double
            || value is decimal;
}

Powinno to obejmować wszystkie typy liczbowe.

Aktualizacja

Wygląda na to, że faktycznie chcesz przeanalizować liczbę z ciągu podczas deserializacji. W takim przypadku prawdopodobnie najlepiej byłoby użyć double.TryParse.

string value = "123.3";
double num;
if (!double.TryParse(value, out num))
    throw new InvalidOperationException("Value is not a number.");

Oczywiście nie obsługiwałoby to bardzo dużych liczb całkowitych / długich miejsc po przecinku, ale w takim przypadku wystarczy dodać dodatkowe wywołania do long.TryParse/ decimal.TryParse/ cokolwiek innego.

Noldorin
źródło
Mój obiekt to int, short, uint, float, double lub cokolwiek innego, co jest liczbą
Piotr Czapla
@Piotr: Ach tak. Wygląda na to, że cię źle zrozumiałem. Zobacz moją zaktualizowaną odpowiedź.
Noldorin
1
@Noldorin: właściwie działałaby również twoja poprzednia wersja kodu; po prostu dodaj kontrolę null i użyj value.ToString (). Nie musisz wtedy sprawdzać wszystkich typów liczbowych.
Fredrik Mörk,
1
@Noldorin Szkoda, że ​​właściwym rozwiązaniem jest ta gadatliwość :(.
Piotr Czapla
1
@Joe: Właściwie nie miałoby to znaczenia, ponieważ ToString używałoby również bieżącej kultury.
Noldorin
36

Zaczerpnięte z bloga Scotta Hanselmana :

public static bool IsNumeric(object expression)
{
    if (expression == null)
    return false;

    double number;
    return Double.TryParse( Convert.ToString( expression
                                            , CultureInfo.InvariantCulture)
                          , System.Globalization.NumberStyles.Any
                          , NumberFormatInfo.InvariantInfo
                          , out number);
}
Saul Dolgin
źródło
6
Problem z tym podejściem polega na tym, że jeśli przekażesz ciąg, który wygląda jak liczba, sformatuje go. Może być ok dla większości ludzi, ale był to dla mnie przystanek na trasie.
Rob Sedgwick,
1
Innym potencjalnym problemem jest to, że nie można przeanalizować wartości min / max dla double. double.Parse(double.MaxValue.ToString())powoduje OverflowException. Możesz temu zaradzić, podając .ToString("R")w tym przypadku modyfikator przesyłania w obie strony , ale to przeciążenie nie jest dostępne, Convert.ToString(...)ponieważ nie znamy typu. Wiem, że to trochę marginalna sprawa, ale natknąłem się na to podczas pisania testów dla mojego własnego .IsNumeric()rozszerzenia. Moim "rozwiązaniem" było dodanie przełącznika sprawdzania typu przed próbą przeanalizowania czegokolwiek. Zobacz moją odpowiedź na to pytanie dla kodu.
Ben
21

Skorzystaj z właściwości IsPrimitive, aby utworzyć przydatną metodę rozszerzenia:

public static bool IsNumber(this object obj)
{
    if (Equals(obj, null))
    {
        return false;
    }

    Type objType = obj.GetType();
    objType = Nullable.GetUnderlyingType(objType) ?? objType;

    if (objType.IsPrimitive)
    {
        return objType != typeof(bool) && 
            objType != typeof(char) && 
            objType != typeof(IntPtr) && 
            objType != typeof(UIntPtr);
    }

    return objType == typeof(decimal);
}

EDYCJA: Naprawiono zgodnie z komentarzami. Typy ogólne zostały usunięte od czasu, gdy pola .GetType () mają typy wartości. Uwzględniono również poprawkę dla wartości dopuszczających wartość null.

Kenan EK
źródło
1
Część ogólna nie daje ci tutaj nic więcej, prawda? masz dostęp tylko do GetType (), który jest dostępny w obiekcie ...
Peter Lillevold,
Zapisuje jedną operację na skrzynce, jeśli jest wywoływana dla typu wartości. Pomyśl o możliwości ponownego wykorzystania.
Kenan EK,
1
Dlaczego nie użyć typeof (T) zamiast obj.GetType, w ten sposób nie otrzymasz NullReferenceException, jeśli ktoś przekaże null typ referencyjny. Możesz również umieścić ogólne ograniczenie na T, aby akceptować tylko typy wartości. Oczywiście, jeśli to zrobisz, zaczniesz mieć dużo informacji w czasie kompilacji.
Trillian
objecti stringnie są typami prymitywnymi.
Wszyscy jesteśmy Monica,
@jnylen: ta odpowiedź była już jakiś czas temu. Wydaje mi się, że w tamtym czasie wykopałem coś ze źródła z odblaskowym szkieletem, ale kto dziś wie ... Naprawiono odpowiedź.
Kenan EK
10

Powyżej znajduje się kilka świetnych odpowiedzi. Oto rozwiązanie typu „wszystko w jednym”. Trzy przeciążenia w różnych okolicznościach.

// Extension method, call for any object, eg "if (x.IsNumeric())..."
public static bool IsNumeric(this object x) { return (x==null ? false : IsNumeric(x.GetType())); }

// Method where you know the type of the object
public static bool IsNumeric(Type type) { return IsNumeric(type, Type.GetTypeCode(type)); }

// Method where you know the type and the type code of the object
public static bool IsNumeric(Type type, TypeCode typeCode) { return (typeCode == TypeCode.Decimal || (type.IsPrimitive && typeCode != TypeCode.Object && typeCode != TypeCode.Boolean && typeCode != TypeCode.Char)); }
Mick Bruno
źródło
rozważ dodanie czeku zerowego
wiero
Naprawdę nie potrzebujesz sprawdzania wartości null - jako metody rozszerzającej nie można jej wywołać z wartością null. Oczywiście ktoś mógłby nadal wywołać normalną funkcję, ale nie jest to oczekiwane użycie metody rozszerzającej.
Mick Bruno,
5
Myślę, że można to nazwać wartością zerową. obiekt obj = null; obj.IsNumeric ();
wiero
Dzięki Weiro, naprawiłem to. Nie zdawałem sobie sprawy, że wywołanie metody rozszerzenia o wartości null było możliwe, ale oczywiście tak!
Mick Bruno
Myślę, że na końcu pierwszego przeciążenia brakuje nawiasu: „return (x == null? False: IsNumeric (x.GetType ()));”
glenn garson
8

Zamiast przesuwać własny, najpewniejszym sposobem Microsoft.VisualBasicsprawdzenia, czy typ wbudowany jest numeryczny, jest prawdopodobnie odwołanie się i wywołanie Information.IsNumeric(object value). Implementacja obsługuje wiele subtelnych przypadków, takich jak char[]ciągi HEX i OCT.

satnhak
źródło
To powinno być na górze!
nawfal
4

Istnieją trzy różne koncepcje:

  • aby sprawdzić, czy jest to liczba (tj. sama wartość liczbowa (zwykle w ramce)), zaznacz typ za pomocą is- na przykładif(obj is int) {...}
  • aby sprawdzić, czy ciąg może zostać przeanalizowany jako liczba; posługiwać sięTryParse()
  • ale jeśli obiekt nie jest liczbą ani łańcuchem, ale podejrzewasz, że ToString()może dać coś, co wygląda jak liczba, wywołaj ToString() i potraktuj to jako ciąg

W obu pierwszych dwóch przypadkach prawdopodobnie będziesz musiał osobno obsłużyć każdy typ liczbowy, który chcesz obsługiwać ( double/ decimal/ int) - na przykład każdy ma inny zakres i dokładność.

Możesz również spojrzeć na wyrażenie regularne, aby szybko sprawdzić przybliżone.

Marc Gravell
źródło
4

Zakładając, że dane wejściowe to ciąg ...

Istnieją 2 sposoby:

użyj Double.TryParse ()

double temp;
bool isNumber = Double.TryParse(input, out temp);

użyj Regex

 bool isNumber = Regex.IsMatch(input,@"-?\d+(\.\d+)?");
Philippe Leybaert
źródło
4

Możesz użyć takiego kodu:

if (n is IConvertible)
  return ((IConvertible) n).ToDouble(CultureInfo.CurrentCulture);
else
  // Cannot be converted.

Jeśli przedmiot jest Int32, Single, Doubleitd. Będzie wykonywać konwersję. Ponadto łańcuch implementuje, IConvertibleale jeśli ciąg nie jest konwertowany na podwójny, FormatExceptionzostanie wyrzucony a.

Martin Liversage
źródło
W rzeczywistości ciągi zostaną przeanalizowane, ale jeśli nie mają prawidłowego formatu, zostanie zgłoszony wyjątek FormatException.
alfoks
@alfoks: Masz całkowitą rację, więc zaktualizowałem odpowiedź.
Martin Liversage,
1

Jeśli twoje wymaganie jest naprawdę

.ToString () zwróci ciąg znaków zawierający cyfry oraz +, - ,.

i chcesz użyć double.TryParse, musisz użyć przeciążenia, które przyjmuje parametr NumberStyles, i upewnij się, że używasz niezmiennej kultury.

Na przykład dla liczby, która może mieć wiodący znak, bez wiodących lub końcowych białych znaków, bez separatora tysięcy i separatora dziesiętnego kropki, użyj:

NumberStyles style = 
   NumberStyles.AllowLeadingSign | 
   NumberStyles.AllowDecimalPoint | 
double.TryParse(input, style, CultureInfo.InvariantCulture, out result);
Joe
źródło
1

Pisząc własną object.IsNumeric()metodę rozszerzenia opartą na odpowiedzi Saula Dolgina na to pytanie, natknąłem się na potencjalny problem polegający na tym, że otrzymasz, OverflowExceptionjeśli spróbujesz z double.MaxValuelub double.MinValue.

Moim „rozwiązaniem” było połączenie zaakceptowanej odpowiedzi od Noldorina z odpowiedzią od Saula Dolgina i dodanie przełącznika dopasowania wzorca przed próbą przeanalizowania czegokolwiek (i skorzystanie z dobroci C # 7, aby trochę uporządkować):

public static bool IsNumeric(this object obj)
{
    if (obj == null) return false;

    switch (obj)
    {
        case sbyte _: return true;
        case byte _: return true;
        case short _: return true;
        case ushort _: return true;
        case int _: return true;
        case uint _: return true;
        case long _: return true;
        case ulong _: return true;
        case float _: return true;
        case double _: return true;
        case decimal _: return true;
    }

    string s = Convert.ToString(obj, CultureInfo.InvariantCulture);

    return double.TryParse(s, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out double _);
}
Ben
źródło
Uważaj na typy dopuszczające wartość null.
Guillermo Prandi
0

Tak, to działa:

object x = 1;
Assert.That(x is int);

W przypadku liczby zmiennoprzecinkowej musiałbyś przetestować przy użyciu typu zmiennoprzecinkowego:

object x = 1f;
Assert.That(x is float);
Peter Lillevold
źródło
To zadziała, jeśli obiekt był intem przed niejawnym lub jawnym rzutowaniem na obiekt. W twoim przykładzie magiczna liczba 1 jest liczbą int, która jest następnie rzutowana na typ zmiennej x .. Gdybyś zrobił obiekt x = 1.0, twoja asercja zwróciłaby fałsz.
Dexter,
Istnieją liczby, które nie są liczbami int.
Fredrik Mörk,
tak, więc chodzi mi po prostu o to, co @Noldorin ma teraz w swojej odpowiedzi.
Peter Lillevold