Jak sprawdzić, czy typ jest prymitywny

162

Mam blok kodu, który serializuje typ do tagu HTML.

Type t = typeof(T); // I pass <T> in as a paramter, where myObj is of type T
tagBuilder.Attributes.Add("class", t.Name);
foreach (PropertyInfo prop in t.GetProperties())
{
    object propValue = prop.GetValue(myObj, null);
    string stringValue = propValue != null ? propValue.ToString() : String.Empty;
    tagBuilder.Attributes.Add(prop.Name, stringValue);
}

Działa to doskonale, z wyjątkiem chcę, żeby to zrobić tylko dla typów pierwotnych, jak int, double, boolitp, i inne typy, które nie są prymitywne, ale może być łatwo jak serializacjistring . Chcę, aby ignorował wszystko inne, takie jak listy i inne typy niestandardowe.

Czy ktoś może zasugerować, jak to zrobić? A może muszę określić typy, na które chcę zezwolić, i włączyć typ właściwości, aby sprawdzić, czy jest dozwolony? To trochę bałaganiarskie, więc byłoby miło, gdybym był bardziej uporządkowany.

DaveDev
źródło
12
System.Stringnie jest typem pierwotnym.
SLaks
3
Lepszym sposobem na to jest całkowite zaniechanie używania leków generycznych. Jeśli obsługujesz niewielką liczbę typów jako dozwolone typy parametrów, po prostu miej tyle przeciążeń. Jeśli obsługujesz dowolny typ, który implementuje ISerializable, napisz metodę inną niż ogólna, która przyjmuje ISerializable. Używaj typów ogólnych do rzeczy, które są faktycznie ogólne ; jeśli typ ma znaczenie, prawdopodobnie nie jest ogólny.
Eric Lippert
@Eric: Dzięki. Zastanawiam się też, czy można zastosować te same kryteria w przypadku liczb? Na przykład, aby napisać funkcje matematyczne, które obsługują wszystkie typy liczbowe, tj. Średnią, sumę, itp. Czy powinny być implementowane przy użyciu opcji Generic czy przeciążenia? Czy ma znaczenie, czy implementacja jest taka sama, czy nie? Ponieważ jest to prawie taka sama operacja dla średniej, sumy dla dowolnego typu liczbowego, prawda?
Joan Venge
1
@Joan: Możliwość pisania ogólnych metod arytmetycznych na typach ograniczonych do implementacji różnych operatorów jest często żądaną funkcją, ale wymaga obsługi CLR i jest zaskakująco skomplikowana. Rozważamy to w przyszłych wersjach języka, ale bez obietnic.
Eric Lippert

Odpowiedzi:

184

Możesz użyć tej właściwości Type.IsPrimitive, ale zachowaj ostrożność, ponieważ są pewne typy, które możemy uważać za prymitywne, ale tak nie jest, na przykład Decimali String.

Edycja 1: Dodano przykładowy kod

Oto przykładowy kod:

if (t.IsPrimitive || t == typeof(Decimal) || t == typeof(String) || ... )
{
    // Is Primitive, or Decimal, or String
}

Edycja 2: As @SLaks komentuje , istnieją inne typy, które być może chcesz również traktować jako prymitywy. Myślę, że będziesz musiał dodawać te odmiany jeden po drugim .

Edycja 3: IsPrimitive = (Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double i Single), Anther Primitive-Like type to check (t == typeof (DateTime ))

Javier
źródło
12
I być może DateTime, TimeSpani DateTimeOffset.
SLaks
Mmmm ... tak, masz rację. Myślę, że będziemy musieli dodać więcej możliwości
Javier
2
Musisz użyć logicznego lub ( ||), a nie bitowego lub ( |).
SLaks
42
Oto metoda rozszerzenia, którą napisałem w celu wygodnego uruchamiania testów opisanych w odpowiedziach @Javier i Michael Petito: gist.github.com/3330614 .
Jonathan
5
Możesz użyć właściwości Type.IsValueType i dodać tylko sprawdzenie dla ciągu.
Matteo Migliore
57

Właśnie znalazłem to pytanie, szukając podobnego rozwiązania, i pomyślałem, że możesz być zainteresowany następującym podejściem przy użyciu System.TypeCodei System.Convert.

Łatwo jest serializować dowolny typ, który jest zamapowany na System.TypeCodeinny niż System.TypeCode.Object, więc możesz zrobić:

object PropertyValue = ...
if(Convert.GetTypeCode(PropertyValue) != TypeCode.Object)
{
    string StringValue = Convert.ToString(PropertyValue);
    ...
}

Zaletą tego podejścia jest to, że nie musisz nazywać każdego innego akceptowalnego typu niepierwotnego. Możesz również nieznacznie zmodyfikować powyższy kod, aby obsłużyć dowolny typ, który implementuje IConvertible.

Michael Petito
źródło
2
To jest świetne, musiałem ręcznie dodać Guiddo własnych celów (jako prymityw w mojej definicji).
Erik Philips,
56

Robimy to w naszym ORM:

Type t;
bool isPrimitiveType = t.IsPrimitive || t.IsValueType || (t == typeof(string));

Wiem, że użycie IsValueTypenie jest najlepszą opcją (możesz mieć własne bardzo złożone struktury), ale działa w 99% przypadków (i obejmuje Nullables).

Alex
źródło
6
Dlaczego potrzebujesz IsPrimitive, jeśli używasz IsValueType? Czy nie wszystkie pierwotne typy wartości są?
JoelFan
5
Typ dziesiętny @JoelFan ma wartość IsPrimitive false, ale IsValueType true
xhafan
3
@xhafan: Odpowiadasz na złe pytanie. Pod decimaltym względem wszystkie struktury są podobne . Ale czy istnieje typ, dla którego IsPrimitivewraca, trueale IsValueTypewraca false? Jeśli nie ma takiego typu, t.IsPrimitivetest jest zbędny.
Lii
6
@Lii masz rację, każdy typ prymitywny ma IsValueTypeustawioną wartość true, więc sprawdzanie IsPrimitivenie jest konieczne. Twoje zdrowie!
xhafan
1
@Veverke Oni tego nie robią. Możesz mieć inny niż pierwotny typ wartości, w którym to przypadku właściwości mają różne wartości.
Michael Petito,
38

Z odpowiedzi @Ronnie Overby i komentarza @jonathanconway napisałem tę metodę, która działa dla Nullable i wyklucza struktury użytkownika.

public static bool IsSimpleType(Type type)
{
    return
        type.IsPrimitive ||
        new Type[] {
            typeof(string),
            typeof(decimal),
            typeof(DateTime),
            typeof(DateTimeOffset),
            typeof(TimeSpan),
            typeof(Guid)
        }.Contains(type) ||
        type.IsEnum ||
        Convert.GetTypeCode(type) != TypeCode.Object ||
        (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && IsSimpleType(type.GetGenericArguments()[0]))
        ;
}

Z następującym TestCase:

struct TestStruct
{
    public string Prop1;
    public int Prop2;
}

class TestClass1
{
    public string Prop1;
    public int Prop2;
}

enum TestEnum { TheValue }

[Test]
public void Test1()
{
    Assert.IsTrue(IsSimpleType(typeof(TestEnum)));
    Assert.IsTrue(IsSimpleType(typeof(string)));
    Assert.IsTrue(IsSimpleType(typeof(char)));
    Assert.IsTrue(IsSimpleType(typeof(Guid)));

    Assert.IsTrue(IsSimpleType(typeof(bool)));
    Assert.IsTrue(IsSimpleType(typeof(byte)));
    Assert.IsTrue(IsSimpleType(typeof(short)));
    Assert.IsTrue(IsSimpleType(typeof(int)));
    Assert.IsTrue(IsSimpleType(typeof(long)));
    Assert.IsTrue(IsSimpleType(typeof(float)));
    Assert.IsTrue(IsSimpleType(typeof(double)));
    Assert.IsTrue(IsSimpleType(typeof(decimal)));

    Assert.IsTrue(IsSimpleType(typeof(sbyte)));
    Assert.IsTrue(IsSimpleType(typeof(ushort)));
    Assert.IsTrue(IsSimpleType(typeof(uint)));
    Assert.IsTrue(IsSimpleType(typeof(ulong)));

    Assert.IsTrue(IsSimpleType(typeof(DateTime)));
    Assert.IsTrue(IsSimpleType(typeof(DateTimeOffset)));
    Assert.IsTrue(IsSimpleType(typeof(TimeSpan)));

    Assert.IsFalse(IsSimpleType(typeof(TestStruct)));
    Assert.IsFalse(IsSimpleType(typeof(TestClass1)));

    Assert.IsTrue(IsSimpleType(typeof(TestEnum?)));
    Assert.IsTrue(IsSimpleType(typeof(char?)));
    Assert.IsTrue(IsSimpleType(typeof(Guid?)));

    Assert.IsTrue(IsSimpleType(typeof(bool?)));
    Assert.IsTrue(IsSimpleType(typeof(byte?)));
    Assert.IsTrue(IsSimpleType(typeof(short?)));
    Assert.IsTrue(IsSimpleType(typeof(int?)));
    Assert.IsTrue(IsSimpleType(typeof(long?)));
    Assert.IsTrue(IsSimpleType(typeof(float?)));
    Assert.IsTrue(IsSimpleType(typeof(double?)));
    Assert.IsTrue(IsSimpleType(typeof(decimal?)));

    Assert.IsTrue(IsSimpleType(typeof(sbyte?)));
    Assert.IsTrue(IsSimpleType(typeof(ushort?)));
    Assert.IsTrue(IsSimpleType(typeof(uint?)));
    Assert.IsTrue(IsSimpleType(typeof(ulong?)));

    Assert.IsTrue(IsSimpleType(typeof(DateTime?)));
    Assert.IsTrue(IsSimpleType(typeof(DateTimeOffset?)));
    Assert.IsTrue(IsSimpleType(typeof(TimeSpan?)));

    Assert.IsFalse(IsSimpleType(typeof(TestStruct?)));
}
Xav987
źródło
1
Jest to dobre podejście, ale Enumnie jest obsługiwane, przetestuj je enum MyEnum { EnumValue }i używaj MyEnum. @Jonathan też używa type.IsValueType. Dzięki temu Enumssą poprawnie wykrywane, ale także Structs. Więc uważaj, jakich prymitywów chcesz.
Apfelkuacha
1
@Apfelkuacha: masz całkowitą rację. Ale zamiast tego type.IsValueType, dlaczego po prostu nie dodać type.IsEnum?
Xav987
masz całkowitą rację. type.IsEnumjest również możliwe. Zaproponowałem zmianę w Twoim poście :)
Apfelkuacha
16

Oto jak to zrobiłem.

   static class PrimitiveTypes
   {
       public static readonly Type[] List;

       static PrimitiveTypes()
       {
           var types = new[]
                          {
                              typeof (Enum),
                              typeof (String),
                              typeof (Char),
                              typeof (Guid),

                              typeof (Boolean),
                              typeof (Byte),
                              typeof (Int16),
                              typeof (Int32),
                              typeof (Int64),
                              typeof (Single),
                              typeof (Double),
                              typeof (Decimal),

                              typeof (SByte),
                              typeof (UInt16),
                              typeof (UInt32),
                              typeof (UInt64),

                              typeof (DateTime),
                              typeof (DateTimeOffset),
                              typeof (TimeSpan),
                          };


           var nullTypes = from t in types
                           where t.IsValueType
                           select typeof (Nullable<>).MakeGenericType(t);

           List = types.Concat(nullTypes).ToArray();
       }

       public static bool Test(Type type)
       {
           if (List.Any(x => x.IsAssignableFrom(type)))
               return true;

           var nut = Nullable.GetUnderlyingType(type);
           return nut != null && nut.IsEnum;
       }
   }
Ronnie Overby
źródło
@RonnieOverby. czy jest jakiś szczególny powód, dla którego używasz IsAssignableFromw swoim teście zamiast zawiera?
johnny 5
6

Również dobra możliwość:

private static bool IsPrimitiveType(Type type)
{
    return (type == typeof(object) || Type.GetTypeCode(type) != TypeCode.Object);
}
k3flo
źródło
Każde wystąpienie Typema właściwość o nazwie IsPrimitive . Zamiast tego powinieneś użyć tego.
Renan
3
Ani Stringteż nie Decimalsą prymitywami.
k3flo
To działa dla mnie, ale zmieniłem nazwę na IsClrType, aby nie mylić jego znaczenia z istniejącym .IsPrimitive w klasie Type
KnarfaLingus
1
Na przykład nie wybierze to Guid ani TimeSpan.
Stanislav
3

Zakładając, że masz taki podpis funkcji:

void foo<T>() 

Możesz dodać ogólne ograniczenie, aby zezwolić tylko na typy wartości:

void foo<T>() where T : struct

Zauważ, że pozwala to nie tylko na typy pierwotne dla T, ale także na dowolny typ wartości.

eWolf
źródło
2

Musiałem serializować typy w celu wyeksportowania ich do XML. Aby to zrobić, przeszedłem przez obiekt i wybrałem pola, które były prymitywne, wyliczeniowe, typy wartości lub możliwe do serializacji. To był wynik mojego zapytania:

Type contextType = context.GetType();

var props = (from property in contextType.GetProperties()
                         let name = property.Name
                         let type = property.PropertyType
                         let value = property.GetValue(context,
                                     (BindingFlags.GetProperty | BindingFlags.GetField | BindingFlags.Public),
                                     null, null, null)
                         where (type.IsPrimitive || type.IsEnum || type.IsValueType || type.IsSerializable)
                         select new { Name = name, Value = value});

Użyłem LINQ do iteracji po typach, a następnie uzyskałem ich nazwę i wartość do przechowywania w tabeli symboli. Kluczem jest klauzula „gdzie”, którą wybrałem do refleksji. Wybrałem typy prymitywne, wyliczeniowe, typy wartości i typy możliwe do serializacji. Pozwoliło to na przejście ciągów i obiektów DateTime zgodnie z oczekiwaniami.

Twoje zdrowie!

JFalcon
źródło
1

To jest to, co mam w mojej bibliotece. Komentarze są mile widziane.

Najpierw sprawdzam IsValueType, ponieważ obsługuje większość typów, a następnie String, ponieważ jest to drugi najpopularniejszy. Nie przychodzi mi do głowy prymityw, który nie jest typem wartości, więc nie wiem, czy ta noga, jeśli kiedykolwiek zostanie trafiona.

  Public Shared Function IsPersistable(Type As System.Type) As Boolean
    With TypeInformation.UnderlyingType(Type)
      Return .IsValueType OrElse Type = GetType(String) OrElse .IsPrimitive
    End With
  End Function

  Public Shared Function IsNullable(ByVal Type As System.Type) As Boolean
    Return (Type.IsGenericType) AndAlso (Type.GetGenericTypeDefinition() Is GetType(Nullable(Of )))
  End Function

  Public Shared Function UnderlyingType(ByVal Type As System.Type) As System.Type
    If IsNullable(Type) Then
      Return Nullable.GetUnderlyingType(Type)
    Else
      Return Type
    End If
  End Function

Wtedy mogę tego użyć w ten sposób:

  Public Shared Function PersistableProperties(Item As System.Type) As IEnumerable(Of System.Reflection.PropertyInfo)
    Return From PropertyInfo In Item.GetProperties()
                     Where PropertyInfo.CanWrite AndAlso (IsPersistable(PropertyInfo.PropertyType))
                     Select PropertyInfo
  End Function
toddmo
źródło
0

Chcę tylko udostępnić moje rozwiązanie. Być może jest to przydatne dla każdego.

public static bool IsPrimitiveType(Type fieldType)
{
   return fieldType.IsPrimitive || fieldType.Namespace.Equals("System");
}
Bahamut
źródło
5
IsPrimitiveType(typeof(System.AccessViolationException)) == true
Ronnie Overby
2
namespace System { class MyNonPrimitiveType { } }
Ronnie Overby
0
public static bool IsPrimitiveType(object myObject)
{
   var myType = myObject.GetType();
   return myType.IsPrimitive || myType.Namespace == null ||  myType.Namespace.Equals("System");
}

Nie zapomnij sprawdzić przestrzeni nazw NULL, ponieważ obiekty anonimowe nie mają przypisanej przestrzeni nazw

iDusko
źródło
0

Oto kolejna realna opcja.

public static bool CanDirectlyCompare(Type type)
{
    return typeof(IComparable).IsAssignableFrom(type) || type.IsPrimitive || type.IsValueType;
}
user2023116
źródło