0

What is the best way to extract any fields or properties from a .NET class that are single-valued (i.e. not an array, collection, etc.)

So far I have

[...]
FieldInfo[] fieldInfos = foo.GetType().GetFields(bindingFlags);
fieldInfos = fieldInfos.Where(f => !f.FieldType.IsArrayOrCollection()).ToArray();
[...]

and this in a static extensions class

public static bool IsArrayOrCollection(this Type testType)
{
    if (testType.IsArray)
        return true;
    if (testType is IEnumerable)
        return true;
    return false;
}

It works but I wonder if there is a better way? Are there any corner cases this will miss?

The rationale for this is that I am storing single-valued members in a database table as key-value pairs. Clearly this won't work for arrays as the value would be a nested type and needs storing in a separate table of its own.

Bit Racketeer
  • 493
  • 1
  • 3
  • 13
  • 2
    a `string` is `IEnumerable` so this is already wrong, you probably want to look for `ICollection` explicitly – millimoose Apr 23 '18 at 14:25
  • I'd take the approach of whitelisting types you know how to roundtrip into a database value, not blacklisting ones you think you can't. – millimoose Apr 23 '18 at 14:25
  • @millimoose in this project there are hundreds of types such as enums. It would be very laborious to list them all. The idea is to be fully generic using a NoSQL DB. – Bit Racketeer Apr 23 '18 at 14:32
  • What about complex types aka other classes? Should they be filtered or not? Checking for valuetype and strings could be another way. – thehennyy Apr 23 '18 at 14:38
  • 1
    I didn't mean whitelisting as in enumerating them all, I meant whitelisting as having your logic have rules on what is valid rather than what is not. You can whitelist "all enums" as opposed to "each and every enum." And using NoSQL because you're too lazy to create a schema sounds like a long-term maintenance nightmare in the making; unless your data actually is mostly nonrelational, which sounds unlikely if there are hundreds of distinct types. But, like, you do you and all. – millimoose Apr 23 '18 at 14:44
  • Yea, I've added a filter for testType.IsClass too subsequently. Good spot. – Bit Racketeer Apr 23 '18 at 14:44
  • @millimoose not too lazy to create a schema - we need a rapid prototyping sketchpad to share info during an R&D phase. We don't have a big enough team to create a schema, nor the need to. And it would change so frequently that updating the DB schema is pointless work that we can avoid. We want to share intermediate calculation results in a rapidly changing project across a team that's in different locations. MongoDB lets us replicate easily. Once it's in stable form, we will develop a schema. Otherwise yes get your point. The DB is nonrelational and it's reporting only. – Bit Racketeer Apr 23 '18 at 14:46
  • @thehennyy checking .IsClass gives a false positive on String. A String is of course a Class but it's not a collection or array. So I've added a really hacky-feeling `if (objectType.Name.ToLower() != "string")` . Ugh. Horrible. Any better ideas? – Bit Racketeer Apr 23 '18 at 15:44
  • 1
    Yeah string has to be considered separately: objectType == typeof(string) – thehennyy Apr 23 '18 at 15:47
  • Suppose also could look at `.IsValueType` - but yes, am for this purpose treating strings/String not as a Reference Type. – Bit Racketeer Apr 23 '18 at 16:08

1 Answers1

0

You can have extension methods as follows:

public static class Extensions
{
    public static IDictionary<string, object> GetSimpleFieldValues(this object obj)
    {
        return obj.GetType()
            .GetFields(BindingFlags.Instance | BindingFlags.Public)
            .Where(f => f.FieldType.IsSimpleType())
            .ToDictionary(k => k.Name, v => v.GetValue(obj));
    }

    public static bool IsSimpleType(this Type type)
    {
        return type.IsValueType 
            || type.IsPrimitive 
            || new Type[] {
                    typeof(String),
                    typeof(Decimal),
                    typeof(DateTime),
                    typeof(DateTimeOffset),
                    typeof(TimeSpan),
                    typeof(Guid)
                }.Contains(type) 
            || Convert.GetTypeCode(type) != TypeCode.Object;
    }
}

So then you can retrieve the key value pairs with:

IDictionary<string, object> values = new YourClass().GetSimpleFieldValues();

Based on this SO answer.

Note this will return false positives for user-defined structs.

thepirat000
  • 12,362
  • 4
  • 46
  • 72