Comment tester si le type est primitif

162

J'ai un bloc de code qui sérialise un type dans une balise 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);
}

Cela fonctionne très bien, sauf que je veux seulement faire cela pour les types primitifs, comme int, double, booletc, et d' autres types qui ne sont pas primitifs , mais peut être sérialisé facilement comme string. Je veux qu'il ignore tout le reste, comme les listes et autres types personnalisés.

Quelqu'un peut-il suggérer comment je fais cela? Ou dois-je spécifier les types que je souhaite autoriser quelque part et activer le type de propriété pour voir s'il est autorisé? C'est un peu brouillon, donc ce serait bien s'il y avait un moyen plus ordonné.

DaveDev
la source
12
System.Stringn'est pas un type primitif.
SLaks
3
La meilleure façon de le faire est de ne pas utiliser du tout de génériques. Si vous prenez en charge un petit nombre de types en tant que types de paramètres légaux, vous avez simplement autant de surcharges. Si vous prenez en charge un type qui implémente ISerializable, écrivez une méthode non générique qui prend un ISerializable. Utilisez des génériques pour des choses qui sont en fait génériques ; si le type compte réellement, ce n'est probablement pas générique.
Eric Lippert
@Eric: Merci, je me demande aussi si vous pouvez utiliser les mêmes critères avec les chiffres? Par exemple pour écrire des fonctions mathématiques qui prennent en charge tous les types numériques, c'est-à-dire Moyenne, Somme, etc. Devraient-elles être implémentées à l'aide de Générique ou de surcharges? Est-ce important que la mise en œuvre soit la même ou non? Parce que c'est à peu près la même opération pour Moyenne, Somme pour tout type numérique, non?
Joan Venge
1
@Joan: Etre capable d'écrire des méthodes arithmétiques génériques sur des types contraints d'implémenter divers opérateurs est une fonctionnalité fréquemment demandée, mais elle nécessite le support CLR et est étonnamment compliquée. Nous l'envisageons pour les futures versions de la langue, mais pas de promesses.
Eric Lippert

Réponses:

184

Vous pouvez utiliser la propriété Type.IsPrimitive, mais soyez prudent car il existe certains types que nous pouvons penser comme primitifs, mais ils ne le sont pas, par exemple Decimalet String.

Edit 1: exemple de code ajouté

Voici un exemple de code:

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

Edit 2: Comme le commente @SLaks , il y a d'autres types que vous voudrez peut-être aussi traiter comme des primitives. Je pense que vous devrez ajouter ces variations une par une .

Edit 3: IsPrimitive = (Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single), type Anther Primitive-Like à vérifier (t == typeof (DateTime ))

Javier
la source
12
Et peut DateTime- être TimeSpan, et DateTimeOffset.
SLaks
Mmmm ... oui, vous avez raison. Je pense que nous devrons ajouter d'autres possibilités
Javier
2
Vous devez utiliser le logique ou ( ||), pas le ou ( |) au niveau du bit .
SLaks
42
Voici une méthode d'extension que j'ai écrite pour exécuter facilement les tests décrits dans les réponses de @Javier et Michael Petito: gist.github.com/3330614 .
Jonathan
5
Vous pouvez utiliser la propriété Type.IsValueType et ajouter uniquement la vérification de la chaîne.
Matteo Migliore
58

Je viens de trouver cette question en cherchant une solution similaire et j'ai pensé que vous pourriez être intéressé par l'approche suivante utilisant System.TypeCodeet System.Convert.

Il est facile de sérialiser tout type mappé à un System.TypeCodeautre que System.TypeCode.Object, vous pouvez donc faire:

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

L'avantage de cette approche est que vous n'avez pas à nommer tous les autres types non primitifs acceptables. Vous pouvez également modifier légèrement le code ci-dessus pour gérer tout type qui implémente IConvertible.

Michael Petito
la source
2
C'est génial, j'ai dû ajouter manuellement Guidpour mes propres besoins (en tant que primitif dans ma définition).
Erik Philips
56

Nous le faisons comme ceci dans notre ORM:

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

Je sais que l'utilisation IsValueTypen'est pas la meilleure option (vous pouvez avoir vos propres structures très complexes) mais cela fonctionne dans 99% des cas (et inclut les Nullables).

Alex
la source
6
Pourquoi avez-vous besoin d'IsPrimitive si vous utilisez IsValueType? Tous les primitifs ne sont-ils pas des types de valeur?
JoelFan
5
Le type décimal @JoelFan a IsPrimitive false, mais IsValueType true
xhafan
3
@xhafan: Vous répondez à la mauvaise question. Toutes les structures sont comme decimalà cet égard. Mais y a-t-il un type pour lequel IsPrimitiveretourne truemais IsValueTyperetourne false? S'il n'y en a pas, le t.IsPrimitivetest n'est pas nécessaire.
Lii
6
@Lii vous avez raison, chaque type primitif a IsValueTypela valeur true, donc la vérification IsPrimitiven'est pas nécessaire. À votre santé!
xhafan
1
@Veverke Ils ne le font pas. Vous pouvez avoir un type de valeur non primitif, auquel cas les propriétés ont des valeurs différentes.
Michael Petito
38

À partir de la réponse @Ronnie Overby et du commentaire @jonathanconway, j'ai écrit cette méthode qui fonctionne pour Nullable et exclut les structures utilisateur.

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]))
        ;
}

Avec le TestCase suivant:

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
la source
1
C'est une bonne approche, mais elle Enumn'est pas prise en charge, testez-la avec enum MyEnum { EnumValue }et utilisez MyEnum. @Jonathan utilise également type.IsValueType. Avec cela, ils Enumssont correctement détectés, mais aussi Structs. Alors faites attention aux primitifs que vous voulez.
Apfelkuacha
1
@Apfelkuacha: vous avez tout à fait raison. Mais au lieu d'utiliser type.IsValueType, pourquoi simplement ne pas ajouter type.IsEnum?
Xav987 le
tu as complètement raison. type.IsEnumest également possible. J'ai suggéré une modification sur votre message :)
Apfelkuacha
16

Voici comment je l'ai fait.

   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
la source
@RonnieOverby. y a-t-il une raison particulière que vous utilisez IsAssignableFromdans votre test au lieu de contient?
johnny 5
6

Aussi une bonne possibilité:

private static bool IsPrimitiveType(Type type)
{
    return (type == typeof(object) || Type.GetTypeCode(type) != TypeCode.Object);
}
k3flo
la source
Chaque instance de Typea une propriété appelée IsPrimitive . Vous devriez utiliser cela à la place.
Renan
3
Ni Stringni Decimalles primitifs.
k3flo
Cela fonctionne pour moi, mais j'ai renommé IsClrType pour ne pas confondre sa signification avec le .IsPrimitive existant sur la classe Type
KnarfaLingus
1
Cela ne choisira pas Guid ou TimeSpan, par exemple.
Stanislav
3

En supposant que vous ayez une signature de fonction comme celle-ci:

void foo<T>() 

Vous pouvez ajouter une contrainte générique pour autoriser uniquement les types valeur:

void foo<T>() where T : struct

Notez que cela autorise non seulement les types primitifs pour T, mais tout type valeur.

eWolf
la source
2

J'avais besoin de sérialiser les types pour les exporter vers XML. Pour ce faire, j'ai parcouru l'objet et opté pour des champs primitifs, enum, de type valeur ou sérialisables. C'était le résultat de ma requête:

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});

J'ai utilisé LINQ pour parcourir les types, puis obtenir leur nom et leur valeur à stocker dans une table de symboles. La clé est dans la clause «où» que j'ai choisie pour la réflexion. J'ai choisi des types primitifs, énumérés, valeur et des types sérialisables. Cela a permis aux chaînes et aux objets DateTime de passer comme prévu.

À votre santé!

JFalcon
la source
1

C'est ce que j'ai dans ma bibliothèque. Les commentaires sont les bienvenus.

Je vérifie d'abord IsValueType, car il gère la plupart des types, puis String, car c'est le deuxième plus courant. Je ne peux pas penser à une primitive qui n'est pas un type valeur, donc je ne sais pas si cette jambe du si jamais est touchée.

  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

Ensuite, je peux l'utiliser comme ceci:

  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
la source
0

Je veux juste partager ma solution. C'est peut-être utile à n'importe qui.

public static bool IsPrimitiveType(Type fieldType)
{
   return fieldType.IsPrimitive || fieldType.Namespace.Equals("System");
}
Bahamut
la source
5
IsPrimitiveType(typeof(System.AccessViolationException)) == true
Ronnie Overpar
2
namespace System { class MyNonPrimitiveType { } }
Ronnie Overpar
0
public static bool IsPrimitiveType(object myObject)
{
   var myType = myObject.GetType();
   return myType.IsPrimitive || myType.Namespace == null ||  myType.Namespace.Equals("System");
}

N'oubliez pas de vérifier l'espace de noms NULL, car les objets anonymes n'ont pas d'espace de noms attribué

iDusko
la source
0

Voici une autre option viable.

public static bool CanDirectlyCompare(Type type)
{
    return typeof(IComparable).IsAssignableFrom(type) || type.IsPrimitive || type.IsValueType;
}
user2023116
la source