如何测试数据类型是否为基本类型

195

我有一段代码,可以将一个类型序列化为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);
}

这个方法很好,但是我希望它只对原始类型像intdoublebool等以及其他易于序列化的类型像string进行操作。我希望它忽略像列表和其他自定义类型一样的所有其他东西。

有人能建议我该如何做到这一点吗?或者我需要在某处指定要允许的类型,并根据属性的类型开关来查看是否允许使用它们?那会有点混乱,所以如果有更简洁的方式就更好了。


17
System.String 不是一个基本类型。 - SLaks
4
更好的做法是根本不使用泛型。如果您支持一些特定类型作为合法参数类型,那么只需编写相应数量的重载方法即可。如果您支持任何实现ISerializable接口的类型,则编写一个非泛型方法,该方法采用一个ISerializable参数。将泛型用于实际上是通用的事物;如果类型确实很重要,那么它可能就不是通用的。 - Eric Lippert
@Eric:谢谢,我也在想是否可以使用相同的标准来处理数字?例如编写支持所有数值类型的数学函数,如平均值、总和等。它们应该使用泛型还是重载实现?实现是否相同并不重要吗?因为对于任何数值类型,计算平均值、总和等操作基本上都是相同的,对吧? - Joan Venge
1
@Joan:能够在实现各种运算符的类型上编写通用算术方法是一个经常被请求的功能,但它需要CLR支持并且非常复杂。我们正在考虑在未来的语言版本中实现它,但不做承诺。 - Eric Lippert
13个回答

208

您可以使用属性 Type.IsPrimitive,但要小心,因为有些类型我们可能认为是原始类型,但实际上不是,例如 DecimalString

编辑 1:添加示例代码

以下是示例代码:

if (t.IsPrimitive || t == typeof(Decimal) || t == typeof(String) || ... )
{
    // Is Primitive, or Decimal, or String
}
编辑2:正如@SLaks所评论的,还有其他类型可能也需要作为基元类型处理。我认为你需要逐一添加这些变化。 编辑3:IsPrimitive = (Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double和Single),另一个类似基元类型的检查(t == typeof(DateTime))。

13
也许还涉及到 DateTimeTimeSpanDateTimeOffset - SLaks
嗯...是的,你说得对。我认为我们需要添加更多的可能性。 - Javier
2
你需要使用逻辑或(||),而不是位或(|)。 - SLaks
50
这是我写的一个扩展方法,方便地运行由@Javier和Michael Petito在答案中描述的测试:https://gist.github.com/3330614。 - Jonathan
7
你可以使用属性Type.IsValueType,并仅添加对字符串的检查。 - Matteo Migliore
显示剩余6条评论

65

我在查找类似解决方案时发现了这个问题,并认为你可能会对以下使用System.TypeCodeSystem.Convert的方法感兴趣。

可以轻松序列化映射到System.TypeCode但不是System.TypeCode.Object的任何类型,因此您可以执行以下操作:

object PropertyValue = ...
if(Convert.GetTypeCode(PropertyValue) != TypeCode.Object)
{
    string StringValue = Convert.ToString(PropertyValue);
    ...
}
这种方法的优点是您不需要命名其他可接受的非基元类型。您还可以稍微修改上面的代码来处理实现IConvertible的任何类型。

2
这太棒了,我不得不手动添加Guid以满足我的需求(作为我定义中的原语)。 - Erik Philips

61
我们的ORM是这样做的:
Type t;
bool isPrimitiveType = t.IsPrimitive || t.IsValueType || (t == typeof(string));

我知道使用IsValueType不是最好的选择(你可能有自己非常复杂的结构体),但它在99%的情况下有效(包括可空类型)。

6
如果您正在使用IsValueType,为什么还需要IsPrimitive?难道不是所有原始类型都是值类型吗? - JoelFan
6
@JoelFan 中的 decimal 类型具有 IsPrimitive 属性为 false,但 IsValueType 属性为 true。 - xhafan
3
@xhafan:您回答了错误的问题。所有结构体都像 decimal 一样。但是有没有一种类型,IsPrimitive 返回 true,但 IsValueType 返回 false?如果没有这样的类型,则 t.IsPrimitive 测试是不必要的。 翻译:您回答了错误的问题。所有的结构体在这方面都类似于 decimal。但是是否存在一种类型,使得 IsPrimitive 返回值为 true,而 IsValueType 返回值为 false 呢?如果不存在这样的类型,则对 t.IsPrimitive 进行测试是没有必要的。 - Lii
7
@Lii你说得对,每个基本类型都将IsValueType设置为true,因此检查IsPrimitive没有必要。干杯! - xhafan
1
@Veverke 不是这样的。你可以有一个非原始值类型,在这种情况下,属性具有不同的值。 - Michael Petito
显示剩余4条评论

56

根据@Ronnie Overby的回复和@jonathanconway的评论,我编写了这个方法,适用于Nullable,但不包括用户结构体。

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

有以下测试用例:

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

1
这是一个不错的方法,但是Enum不被支持,请使用enum MyEnum { EnumValue }MyEnum进行测试。 @Jonathan 也在使用 type.IsValueType。通过这个方式,Enums 被正确地检测出来了,但是也会检测出Structs。所以请注意你想要的基元类型。 - Coden
1
@Apfelkuacha:你是完全正确的。但是,为什么不直接添加 type.IsEnum,而使用 type.IsValueType 呢? - Xav987
你是完全正确的。type.IsEnum 也是可能的。我已经在你的帖子上建议了一个编辑 :) - Coden
Convert.GetTypeCode(type) 总是返回 Object。 另外,我会消除那个用于包含检查的数组分配,并将那些类型检查转换为布尔表达式。 - Ronnie Overby

33
这是我实现它的方法。
   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年更新

参考@Xav987的答案,这个做法表现更好且代码更少。

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


@RonnieOverby。你在测试中使用IsAssignableFrom而不是contains,有什么特别的原因吗? - johnny 5

6

还有一个很好的可能性:

private static bool IsPrimitiveType(Type type)
{
    return (type == typeof(object) || Type.GetTypeCode(type) != TypeCode.Object);
}

每个 Type 实例都有一个名为 IsPrimitive 的属性。您应该使用它。 - Geeky Guy
3
"String"和"Decimal"都不是原始数据类型。 - k3flo
这对我很有效,但我将其重命名为IsClrType,以免与Type类上现有的.IsPrimitive含义混淆。 - KnarfaLingus
1
例如,这不会选择Guid或TimeSpan。 - Stanislav

3
假设您有一个如下的函数签名:

假定您拥有这样的一个函数签名:

void foo<T>() 

您可以添加一个通用约束以仅允许值类型:
void foo<T>() where T : struct

请注意,这不仅允许使用T作为原始类型,还允许使用任何值类型。

2

我需要将类型序列化,以便将它们导出到XML。为此,我遍历了对象并选择了原始类型、枚举、值类型或可序列化的字段。以下是我的查询结果:

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

我使用了LINQ来遍历类型,然后获取它们的名称和值并存储在符号表中。我选择了反射的'where'子句中的关键字。我选择了基元、枚举、值类型和可序列化类型。这使得字符串和DateTime对象按照我预期的方式传递。

干杯!


1
public static bool IsPrimitiveType(object myObject)
{
   var myType = myObject.GetType();
   return myType.IsPrimitive || myType.Namespace == null ||  myType.Namespace.Equals("System");
}

不要忘记检查NULL命名空间,因为匿名对象没有分配命名空间。


1

这是我在我的库中拥有的内容。欢迎评论。

我首先检查IsValueType,因为它处理大多数类型,然后是String,因为它是第二常见的。我想不出一个原始类型不是值类型,所以我不知道if语句的这个分支是否会被执行。

  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

那么我可以像这样使用它:

  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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接