192

I have a block of code that serializes a type into a Html tag.

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

This works great, except I want it to only do this for primitive types, like int, double, bool etc, and other types that aren't primitive but can be serialized easily like string. I want it to ignore everything else like Lists & other custom types.

Can anyone suggest how I do this? Or do I need to specify the types I want to allow somewhere and switch on the property's type to see if it's allowed? That's a little messy, so it'd be nice if I there was a tidier way.

DaveDev
  • 41,155
  • 72
  • 223
  • 385
  • 17
    `System.String` is not a primitive type. – SLaks Mar 14 '10 at 15:02
  • 4
    The better way to do it is to not use generics at all. If you support a small number of types as legal parameter types then simply have that many overloads. If you support any type that implements ISerializable, then write a non-generic method that takes an ISerializable. Use generics for things which are actually *generic*; if the type actually matters, its probably not generic. – Eric Lippert Mar 14 '10 at 20:37
  • @Eric: Thanks, I am also wondering if you can use the same criteria with numerics? For instance to write mathematical functions that support all numeric types, i.e. Average, Sum, etc. Should they be implemented using Generic or overloads? Does it matter whether the implementation is the same or not? Because it's pretty much the same operation for Average, Sum for any numeric type, right? – Joan Venge Mar 15 '10 at 20:53
  • 1
    @Joan: Being able to write generic arithmetic methods on types constrained to implement various operators is a frequently requested feature, but it requires CLR support and is surprisingly complicated. We're considering it for future versions of the language, but no promises. – Eric Lippert Mar 16 '10 at 05:45

13 Answers13

207

You can use the property Type.IsPrimitive, but be carefull because there are some types that we can think that are primitives, but they aren´t, for example Decimal and String.

Edit 1: Added sample code

Here is a sample code:

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

Edit 2: As @SLaks comments, there are other types that maybe you want to treat as primitives, too. I think that you´ll have to add this variations one by one.

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

Community
  • 1
  • 1
Javier
  • 4,051
  • 2
  • 22
  • 20
65

I just found this question while looking for a similar solution, and thought you might be interested in the following approach using System.TypeCode and System.Convert.

It is easy to serialize any type that is mapped to a System.TypeCode other than System.TypeCode.Object, so you could do:

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

The advantage with this approach is you don't have to name every other acceptable non-primitive type. You could also modify the above code slightly to handle any type that implements IConvertible.

Michael Petito
  • 12,891
  • 4
  • 40
  • 54
61

We do it like this in our ORM:

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

I know that using IsValueType is not the best option (you can have your own very complex structs) but it works in 99% cases (and includes Nullables).

MGOwen
  • 6,562
  • 13
  • 58
  • 67
Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
  • 6
    Why do you need IsPrimitive if you are using IsValueType? Are not all primitives value types? – JoelFan Oct 29 '14 at 19:22
  • 6
    @JoelFan decimal type has IsPrimitive false, but IsValueType true – xhafan Dec 09 '14 at 08:04
  • 3
    @xhafan: You answer the wrong question. All structs are like `decimal` in that regard. But are there a type for which `IsPrimitive` returns `true` but `IsValueType` returns `false`? If there are no such type then the `t.IsPrimitive` test is unnecessary. – Lii Jan 13 '15 at 08:21
  • 7
    @Lii you are right, every primitive type has `IsValueType` set to true, so checking for `IsPrimitive` is not need. Cheers! – xhafan Jan 13 '15 at 14:17
  • So what is then the answer to "why does the framework provide 2 properties that represent the very same data" ? – Veverke Apr 20 '15 at 14:33
  • 1
    @Veverke They don't. You can have a non-primitive value type, in which case the properties have different values. – Michael Petito Jul 09 '15 at 02:33
  • @MichaelPetito: am I correct in saying that the only scenario such thing could happen is when you have a user-defined struct ? – Veverke Jul 09 '15 at 07:44
  • 1
    @Veverke No. For instance, `System.Decimal` is a non-primitive value type. So is `System.Guid`. – Michael Petito Jul 10 '15 at 13:19
  • @MichaelPetito: but both are structs. I should have stated "... only scenario this could happen is with a struct". – Veverke Jul 12 '15 at 09:26
54

From @Ronnie Overby response and @jonathanconway comment, I wrote this method that works for Nullable, and exclude user structs.

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

With the following 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?)));
}
Coden
  • 2,579
  • 1
  • 18
  • 25
Xav987
  • 1,182
  • 1
  • 15
  • 17
  • 1
    This is a good approach, but `Enum` is not supported, test it with `enum MyEnum { EnumValue }` and using `MyEnum`. @Jonathan is also using `type.IsValueType`. With that the `Enums` are correctly detected, but also `Structs`. So watch out what primitives you want. – Coden Jan 10 '20 at 08:36
  • 1
    @Apfelkuacha: you are totally right. But instead using `type.IsValueType`, why simply not add `type.IsEnum`? – Xav987 Jan 27 '20 at 15:47
  • you are totally right. `type.IsEnum` is also possible. I've suggested an edit on your post :) – Coden Jan 28 '20 at 12:11
  • `Convert.GetTypeCode(type)` always returns `Object`. Also, I'd get rid of that Array allocation for the contains check and convert those type checks to boolean expressions. – Ronnie Overby Aug 31 '20 at 13:07
33

Here's how I did it.

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

2023 update

Derived from @Xav987's answer, this performs better and has less code.

    static readonly ConcurrentDictionary<Type, bool> IsSimpleTypeCache = new ConcurrentDictionary<System.Type, bool>();

    public static bool IsSimpleType(Type type)
    {
        return IsSimpleTypeCache.GetOrAdd(type, t =>
            type.IsPrimitive ||
            type.IsEnum ||
            type == typeof(string) ||
            type == typeof(decimal) ||
            type == typeof(DateTime) ||
            type == typeof(DateOnly) ||
            type == typeof(TimeOnly) ||
            type == typeof(DateTimeOffset) ||
            type == typeof(TimeSpan) ||
            type == typeof(Guid) ||
            IsNullableSimpleType(type));

        static bool IsNullableSimpleType(Type t)
        {
            var underlyingType = Nullable.GetUnderlyingType(t);
            return underlyingType != null && IsSimpleType(underlyingType);
        }
    }

Ronnie Overby
  • 45,287
  • 73
  • 267
  • 346
  • @RonnieOverby. is there any paticular reason you use `IsAssignableFrom` in your test instead of contains? – johnny 5 Mar 19 '18 at 04:10
6

Also a good possibility:

private static bool IsPrimitiveType(Type type)
{
    return (type == typeof(object) || Type.GetTypeCode(type) != TypeCode.Object);
}
k3flo
  • 355
  • 4
  • 9
3

Assuming you have a function signature like this:

void foo<T>() 

You could add a generic constraint to allow value types only:

void foo<T>() where T : struct

Notice that this allows not only primitive types for T, but any value type.

eflorico
  • 3,589
  • 2
  • 30
  • 41
2

I had a need to serialize types for the purposes of exporting them to XML. To do this, I iterated through the object and opted for fields that were primitive, enum, value types or serializable. This was the result of my query:

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

I used LINQ to iterate through the types, then get their name and value to store in a symbol table. The key is in the 'where' clause that I chose for reflection. I chose primitive, enumerated, value types and serializable types. This allowed for strings and DateTime objects to come through as I expected.

Cheers!

JFalcon
  • 179
  • 1
  • 10
1

This is what I have in my library. Comments are welcome.

I check IsValueType first, since it handles most types, then String, since it's the second most common. I can't think of a primitive that isn't a value type, so I don't know if that leg of the if ever gets hit.

  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

Then I can use it like this:

  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
  • 20,682
  • 14
  • 97
  • 107
1
public static bool IsPrimitiveType(object myObject)
{
   var myType = myObject.GetType();
   return myType.IsPrimitive || myType.Namespace == null ||  myType.Namespace.Equals("System");
}

Don't forget to check NULL namespace, because anonymous objects don't have assigned namespace

iDusko
  • 53
  • 5
0

I just want to share my solution. Perhaps it's useful to anyone.

public static bool IsPrimitiveType(Type fieldType)
{
   return fieldType.IsPrimitive || fieldType.Namespace.Equals("System");
}
Bahamut
  • 195
  • 1
  • 6
0

Here is another viable option.

public static bool CanDirectlyCompare(Type type)
{
    return typeof(IComparable).IsAssignableFrom(type) || type.IsPrimitive || type.IsValueType;
}
user2023116
  • 423
  • 2
  • 6
  • 16
0

An Extension method which works only for Primitive types, String, Decimal, DateTime, DateTimeOffset, TimeSpan and Guid.
You can add type.IsValueType in OR if you want to include generics, structs and enums.

    private static readonly Type[] PrimitiveLikeTypes = new[]
    {
                typeof(string),
                typeof(decimal),
                typeof(DateTime),
                typeof(DateTimeOffset),
                typeof(TimeSpan),
                typeof(Guid)
    };

    /// <summary>
    /// Determine whether a type is simple (Primitive, String, Decimal, DateTime, etc) 
    /// or complex (i.e. structs, Enums, custom class with public properties and methods).
    /// Returns false for structs and Enums
    /// </summary>
    /// <param name="type">System.Type</param>
    /// <returns> boolean value indicating whether the type is simple or not</returns>
    public static bool IsSimpleType(this Type type)
    {
        return type.IsPrimitive || PrimitiveLikeTypes.Contains(type);
    }
    
Jay Shah
  • 3,553
  • 1
  • 27
  • 26