C # - jak określić, czy typ jest liczbą


105

Czy istnieje sposób określenia, czy dany typ .Net jest liczbą? Na przykład: System.UInt32/UInt16/Doubleto wszystkie liczby. Chcę uniknąć długiego przełączania na Type.FullName.


4
Duplikat wielu, wielu, wielu. Dlaczego to jeszcze nie zostało zamknięte?
Noldorin

2
Duplikat stackoverflow.com/questions/1130698 i bardzo blisko kilku innych.
Henk Holterman

Odpowiedzi:


110

Spróbuj tego:

Type type = object.GetType();
bool isNumber = (type.IsPrimitiveImple && type != typeof(bool) && type != typeof(char));

Typy pierwotne to Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double i Single.

Biorąc roztwór Guillaume trochę dalej:

public static bool IsNumericType(this object o)
{   
  switch (Type.GetTypeCode(o.GetType()))
  {
    case TypeCode.Byte:
    case TypeCode.SByte:
    case TypeCode.UInt16:
    case TypeCode.UInt32:
    case TypeCode.UInt64:
    case TypeCode.Int16:
    case TypeCode.Int32:
    case TypeCode.Int64:
    case TypeCode.Decimal:
    case TypeCode.Double:
    case TypeCode.Single:
      return true;
    default:
      return false;
  }
}

Stosowanie:

int i = 32;
i.IsNumericType(); // True

string s = "Hello World";
s.IsNumericType(); // False

2
Więc decimaltyp nie jest numeryczny?
LukeH

2
@Xaero: Nie mam wątpliwości, że decimal jest to numeryczne. To, że nie jest prymitywem, nie oznacza, że ​​nie jest numeryczne. Twój kod musi to uwzględniać.
LukeH

2
Musiałoby to zostać przeprojektowane dla nowych typów liczbowych w .NET 4.0, które nie mają kodów typów.
Jon Skeet

7
Jak możesz mnie negatywnie ocenić w odpowiedzi opartej na aktualnej technologii. Może w .NET 62 int zostanie usunięte - czy zamierzasz głosować w dół wszystkie odpowiedzi z int?
Philip Wallace

1
@DiskJunky Przepraszamy, przyjacielu. To było prawie trzy lata temu i nie pamiętam ich zawartości.
kdbanman

93

Nie używaj przełącznika - po prostu użyj zestawu:

HashSet<Type> NumericTypes = new HashSet<Type>
{
    typeof(decimal), typeof(byte), typeof(sbyte),
    typeof(short), typeof(ushort), ...
};

EDYCJA: Jedną z zalet tego w porównaniu z używaniem kodu typu jest to, że kiedy nowe typy liczbowe są wprowadzane do .NET (np. BigInteger i Complex ), jest to łatwe do dostosowania - podczas gdy te typy nie otrzymają kodu typu.


4
i jak użyłbyś HashSet?
RvdK

8
NumericTypes.Contains (cokolwiek)?
mqp

3
bool isANumber = NumericTypes.Contains (classInstance.GetType ());
Yuriy Faktorovich

Pomyślałby, że kompilator wykona niejawną konwersję instrukcji switch na hashset.
Rolf Kristensen

6
@RolfKristensen: Po switchprostu nie działa Type, więc nie możesz. Możesz TypeCodeoczywiście włączyć , ale to inna sprawa.
Jon Skeet,

69

Żadne z rozwiązań nie bierze pod uwagę wartości Nullable.

Zmodyfikowałem nieco rozwiązanie Jona Skeeta:

    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(uint),
        typeof(double),
        typeof(decimal),
        ...
    };

    internal static bool IsNumericType(Type type)
    {
        return NumericTypes.Contains(type) ||
               NumericTypes.Contains(Nullable.GetUnderlyingType(type));
    }

Wiem, że mógłbym po prostu dodać same wartości null do mojego HashSet. Ale to rozwiązanie pozwala uniknąć niebezpieczeństwa zapomnienia o dodaniu określonego Nullable do listy.

    private static HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),
        typeof(int?),
        ...
    };

2
Czy typ dopuszczający wartość null jest naprawdę liczbowy? O ile wiem, zero nie jest liczbą.
IllidanS4 chce, aby Monica wróciła

2
To zależy od tego, co chcesz osiągnąć. W moim przypadku musiałem również uwzględnić wartości null. Ale mógłbym też pomyśleć o sytuacjach, w których nie jest to pożądane zachowanie.
Jürgen Steinblock

Dobry! Traktowanie liczby dopuszczającej wartość null jako liczby jest bardzo przydatne w sprawdzaniu poprawności danych wejściowych interfejsu użytkownika.
guogangj

1
@ IllidanS4 Kontrola jest włączona Wpisz, a nie wartość. W większości przypadków typy liczbowe dopuszczające wartość zerową powinny być traktowane jako numeryczne. Oczywiście, jeśli sprawdzanie dotyczyło value, a value jest null, to tak, nie powinno być traktowane jako numeryczne.
nawfal

40
public static bool IsNumericType(Type type)
{
  switch (Type.GetTypeCode(type))
  {
    case TypeCode.Byte:
    case TypeCode.SByte:
    case TypeCode.UInt16:
    case TypeCode.UInt32:
    case TypeCode.UInt64:
    case TypeCode.Int16:
    case TypeCode.Int32:
    case TypeCode.Int64:
    case TypeCode.Decimal:
    case TypeCode.Double:
    case TypeCode.Single:
      return true;
    default:
      return false;
  }
}

Uwaga o usuniętej optymalizacji (zobacz komentarze enzi) A jeśli naprawdę chcesz ją zoptymalizować (utrata czytelności i trochę bezpieczeństwa ...):

public static bool IsNumericType(Type type)
{
  TypeCode typeCode = Type.GetTypeCode(type);
  //The TypeCode of numerical types are between SByte (5) and Decimal (15).
  return (int)typeCode >= 5 && (int)typeCode <= 15;
}


13
Wiem, że ta odpowiedź jest stara, ale niedawno trafiłem na taki przełącznik: nie stosuj sugerowanej optymalizacji! Spojrzałem na kod IL wygenerowany z takiego przełącznika i zauważyłem, że kompilator już stosuje optymalizację (w IL 5 jest odejmowany od kodu typu, a następnie wartości od 0 do 10 są uważane za prawdziwe). Dlatego przełącznik powinien być używany, aby był bardziej czytelny, bezpieczniejszy i równie szybki.
enzi

1
Jeśli faktycznie chcesz go zoptymalizować i nie dbasz o czytelność, optymalnym kodem byłoby return unchecked((uint)Type.GetTypeCode(type) - 5u) <= 10u;usunięcie gałęzi wprowadzonej przez &&.
AnorZaken,

14

Zasadniczo rozwiązanie Skeeta, ale możesz go ponownie użyć z typami Nullable w następujący sposób:

public static class TypeHelper
{
    private static readonly HashSet<Type> NumericTypes = new HashSet<Type>
    {
        typeof(int),  typeof(double),  typeof(decimal),
        typeof(long), typeof(short),   typeof(sbyte),
        typeof(byte), typeof(ulong),   typeof(ushort),  
        typeof(uint), typeof(float)
    };

    public static bool IsNumeric(Type myType)
    {
       return NumericTypes.Contains(Nullable.GetUnderlyingType(myType) ?? myType);
    }
}

9

Podejście oparte na propozycji Philipa , wzbogacone o kontrolę typu wewnętrznego SFun28 dla Nullabletypów:

public static class IsNumericType
{
    public static bool IsNumeric(this Type type)
    {
        switch (Type.GetTypeCode(type))
        {
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Single:
                return true;
            case TypeCode.Object:
                if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    return Nullable.GetUnderlyingType(type).IsNumeric();
                    //return IsNumeric(Nullable.GetUnderlyingType(type));
                }
                return false;
            default:
                return false;
        }
    }
}

Dlaczego to? Musiałem sprawdzić, czy dany Type typejest typem liczbowym, a nie, czy dowolny object ojest liczbowy.


4

Z C # 7 ta metoda daje mi lepszą wydajność niż włączanie obudowy TypeCodei HashSet<Type>:

public static bool IsNumeric(this object o) => o is byte || o is sbyte || o is ushort || o is uint || o is ulong || o is short || o is int || o is long || o is float || o is double || o is decimal;

Testy są następujące:

public static class Extensions
{
    public static HashSet<Type> NumericTypes = new HashSet<Type>()
    {
        typeof(byte), typeof(sbyte), typeof(ushort), typeof(uint), typeof(ulong), typeof(short), typeof(int), typeof(long), typeof(decimal), typeof(double), typeof(float)
    };

    public static bool IsNumeric1(this object o) => NumericTypes.Contains(o.GetType());

    public static bool IsNumeric2(this object o) => o is byte || o is sbyte || o is ushort || o is uint || o is ulong || o is short || o is int || o is long || o is decimal || o is double || o is float;

    public static bool IsNumeric3(this object o)
    {
        switch (o)
        {
            case Byte b:
            case SByte sb:
            case UInt16 u16:
            case UInt32 u32:
            case UInt64 u64:
            case Int16 i16:
            case Int32 i32:
            case Int64 i64:
            case Decimal m:
            case Double d:
            case Single f:
                return true;
            default:
                return false;
        }
    }

    public static bool IsNumeric4(this object o)
    {
        switch (Type.GetTypeCode(o.GetType()))
        {
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Single:
                return true;
            default:
                return false;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {           
        var count = 100000000;

        //warm up calls
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric1();
        }
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric2();
        }
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric3();
        }
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric4();
        }

        //Tests begin here
        var sw = new Stopwatch();
        sw.Restart();
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric1();
        }
        sw.Stop();

        Debug.WriteLine(sw.ElapsedMilliseconds);

        sw.Restart();
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric2();
        }
        sw.Stop();

        Debug.WriteLine(sw.ElapsedMilliseconds);

        sw.Restart();
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric3();
        }
        sw.Stop();

        Debug.WriteLine(sw.ElapsedMilliseconds);

        sw.Restart();
        for (var i = 0; i < count; i++)
        {
            i.IsNumeric4();
        }
        sw.Stop();

        Debug.WriteLine(sw.ElapsedMilliseconds);
    }

3

Możesz użyć Type.IsPrimitive, a następnie posortować typy Booleani Char, coś takiego:

bool IsNumeric(Type type)
{
    return type.IsPrimitive && type!=typeof(char) && type!=typeof(bool);
}

EDIT : Możesz wyłączyć IntPtri UIntPtrrodzajów, jak również, jeśli nie uważają ich za numeryczny.


1
Więc decimaltyp nie jest numeryczny?
LukeH

Ups ... no cóż, wydaje się, że rozwiązanie Guillaume jest w końcu najlepsze.
Konamiman

3

Rozszerzenie typu z obsługą typu null.

public static bool IsNumeric(this Type type)
    {
        if (type == null) { return false; }

        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            type = type.GetGenericArguments()[0];
        }

        switch (Type.GetTypeCode(type))
        {
            case TypeCode.Byte:
            case TypeCode.SByte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
            case TypeCode.Decimal:
            case TypeCode.Double:
            case TypeCode.Single:
                return true;
            default:
                return false;
        }
    }

1

Krótka odpowiedź: nie.

Dłuższa odpowiedź: nie.

Faktem jest, że wiele różnych typów w języku C # może zawierać dane liczbowe. Jeśli nie wiesz, czego się spodziewać (Int, Double, itp.), Musisz użyć „długiej” instrukcji case.


1

To również może działać. Możesz jednak chcieć wykonać to za pomocą Type.Parse, aby następnie rzucić go tak, jak chcesz.

public bool IsNumeric(object value)
{
    float testValue;
    return float.TryParse(value.ToString(), out testValue);
}

1

Zmodyfikowane Skeet tych i rozwiązanie arviman za wykorzystanie Generics, Reflectionoraz C# v6.0.

private static readonly HashSet<Type> m_numTypes = new HashSet<Type>
{
    typeof(int),  typeof(double),  typeof(decimal),
    typeof(long), typeof(short),   typeof(sbyte),
    typeof(byte), typeof(ulong),   typeof(ushort),
    typeof(uint), typeof(float),   typeof(BigInteger)
};

Śledzony przez:

public static bool IsNumeric<T>( this T myType )
{
    var IsNumeric = false;

    if( myType != null )
    {
        IsNumeric = m_numTypes.Contains( myType.GetType() );
    }

    return IsNumeric;
}

Użycie do (T item):

if ( item.IsNumeric() ) {}

null zwraca false.


1

Zmiana jest trochę powolna, ponieważ za każdym razem metody w najgorszej sytuacji będą przechodzić przez wszystkie typy. Myślę, że korzystanie z Dictonary jest przyjemniejsze, w takiej sytuacji będziesz miał O(1):

public static class TypeExtensions
{
    private static readonly HashSet<Type> NumberTypes = new HashSet<Type>();

    static TypeExtensions()
    {
        NumberTypes.Add(typeof(byte));
        NumberTypes.Add(typeof(decimal));
        NumberTypes.Add(typeof(double));
        NumberTypes.Add(typeof(float));
        NumberTypes.Add(typeof(int));
        NumberTypes.Add(typeof(long));
        NumberTypes.Add(typeof(sbyte));
        NumberTypes.Add(typeof(short));
        NumberTypes.Add(typeof(uint));
        NumberTypes.Add(typeof(ulong));
        NumberTypes.Add(typeof(ushort));
    }

    public static bool IsNumber(this Type type)
    {
        return NumberTypes.Contains(type);
    }
}

1

Wypróbuj pakiet NuGet TypeSupport dla języka C #. Obsługuje wykrywanie wszystkich typów liczbowych (wśród wielu innych funkcji):

var extendedType = typeof(int).GetExtendedType();
Assert.IsTrue(extendedType.IsNumericType);

Nie znałem tego pakietu. W wielu przypadkach wydaje się ratunkiem życia, aby uniknąć pisania własnego kodu dla rodzaju operacji wymaganych przez PO. Dzięki !
AFract

0

Niestety, te typy nie mają wiele wspólnego, poza wszystkimi typami wartości. Ale aby uniknąć długich przypadków przełączania, możesz po prostu zdefiniować listę tylko do odczytu ze wszystkimi tymi typami, a następnie po prostu sprawdzić, czy dany typ znajduje się na liście.


0

Wszystkie są typami wartości (z wyjątkiem bool i może enum). Możesz więc po prostu użyć:

bool IsNumberic(object o)
{
    return (o is System.ValueType && !(o is System.Boolean) && !(o is System.Enum))
}

1
To zwróci prawdę dla każdego zdefiniowanego przez użytkownika struct... Nie sądzę, że tego chcesz.
Dan Tao

1
Masz rację. Wbudowane typy liczbowe są również strukturami. Więc lepiej idź z porównaniem Prymitywów.
MandoMando

0

EDYCJA: Cóż, zmodyfikowałem poniższy kod, aby był bardziej wydajny, a następnie przeprowadziłem testy opublikowane przez @Hugo. Prędkości są mniej więcej na równi z JEŻELI @ Hugo przy użyciu ostatniego elementu w jego sekwencji (dziesiętne). Jeśli jednak użyje pierwszego elementu „bajt”, to bierze ciasto, ale wyraźnie porządek ma znaczenie, jeśli chodzi o wydajność. Chociaż użycie poniższego kodu jest łatwiejsze do napisania i bardziej spójne pod względem kosztów, nie jest jednak możliwe do utrzymania ani zabezpieczenia w przyszłości.

Wygląda na to, że przejście z Type.GetTypeCode () na Convert.GetTypeCode () drastycznie przyspieszyło wydajność, około 25%, VS Enum.Parse (), które było około 10 razy wolniejsze.


Wiem, że ten post jest stary, ale JEŚLI użyjesz metody wyliczenia TypeCode, najłatwiej (i prawdopodobnie najtańszy) będzie coś takiego:

public static bool IsNumericType(this object o)
{   
  var t = (byte)Convert.GetTypeCode(o);
  return t > 4 && t < 16;
}

Biorąc pod uwagę następującą definicję wyliczenia dla TypeCode:

public enum TypeCode
{
    Empty = 0,
    Object = 1,
    DBNull = 2,
    Boolean = 3,
    Char = 4,
    SByte = 5,
    Byte = 6,
    Int16 = 7,
    UInt16 = 8,
    Int32 = 9,
    UInt32 = 10,
    Int64 = 11,
    UInt64 = 12,
    Single = 13,
    Double = 14,
    Decimal = 15,
    DateTime = 16,
    String = 18
}

Nie przetestowałem go dokładnie, ale w przypadku podstawowych typów numerycznych C # wydaje się, że to obejmuje. Jednak, jak wspomniał @JonSkeet, to wyliczenie nie jest aktualizowane dla dodatkowych typów dodanych do .NET w przyszłości.


-1

ups! Źle przeczytaj pytanie! Osobiście kroczyłby z Skeetem .


HRM, dźwięki tak, jak chcesz, aby DoSomethingna TypeTwoich danych. Oto co możesz zrobić

public class MyClass
{
    private readonly Dictionary<Type, Func<SomeResult, object>> _map = 
        new Dictionary<Type, Func<SomeResult, object>> ();

    public MyClass ()
    {
        _map.Add (typeof (int), o => return SomeTypeSafeMethod ((int)(o)));
    }

    public SomeResult DoSomething<T>(T numericValue)
    {
        Type valueType = typeof (T);
        if (!_map.Contains (valueType))
        {
            throw new NotSupportedException (
                string.Format (
                "Does not support Type [{0}].", valueType.Name));
        }
        SomeResult result = _map[valueType] (numericValue);
        return result;
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.