Gweaths
Gweaths

Reputation: 85

Get all properties of an object using reflection - but only the properties where the object has a value for them (not default value or null)

So I'm wanting to return the properties of an object, either generic or hard coded typeof(User) for e.g

However i only want to return the properties where the object I'm getting the properties for, has a value set against it, not the default value nor null. The reason for this is so that i can use these properties only to build an expression to only check these properties against columns in our database for items.

I tried something like this, however it still brings back all the values,

public User AutomatedUser {get;set;} // some properties of this will populated elsewhere

var props = typeof(User).GetProperties()
            .Where(pi => pi.GetValue(AutomatedFromUser) != pi.PropertyType.GetDefault());

I then found this method on the forum for getting default values of types, as compiler won't allow != default(pi.PropertyType) as "Pi" is a variable. Method below...

public static object GetDefault(this Type type)
    {
        // If no Type was supplied, if the Type was a reference type, or if the Type was a System.Void, return null
        if (type == null || !type.IsValueType || type == typeof(void))
            return null;

        // If the supplied Type has generic parameters, its default value cannot be determined
        if (type.ContainsGenericParameters)
            throw new ArgumentException(
                "{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe supplied value type <" + type +
                "> contains generic parameters, so the default value cannot be retrieved");

        // If the Type is a primitive type, or if it is another publicly-visible value type (i.e. struct), return a 
        //  default instance of the value type
        if (type.IsPrimitive || !type.IsNotPublic)
        {
            try
            {
                return Activator.CreateInstance(type);
            }
            catch (Exception e)
            {
                throw new ArgumentException(
                    "{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe Activator.CreateInstance method could not " +
                    "create a default instance of the supplied value type <" + type +
                    "> (Inner Exception message: \"" + e.Message + "\")", e);
            }
        }

        // Fail with exception
        throw new ArgumentException("{" + MethodInfo.GetCurrentMethod() + "} Error:\n\nThe supplied value type <" + type +
                                    "> is not a publicly-visible type, so the default value cannot be retrieved");
    }
}

Any tips or help would be greatly appreciated as to why this wouldn't be working, or where i'm going wrong.

Upvotes: 1

Views: 1217

Answers (1)

pinkfloydx33
pinkfloydx33

Reputation: 12739

The problem you are having is related to boxing and the fact that == performs reference equality. Both PropertyInfo.GetValue and your GetDefault function return object, so your value types will be boxed. This means that even if both values are zero, they will be placed into two seperate boxes. Each of those boxes is a different object and thus reference equality returns false.

Consider the following:

object x = 0;
object y = 0;
Console.WriteLine(x == y); // prints False

The solution is to call object.Equals (either the instance or static version) instead.

object x = 0;
object y = 0;
Console.WriteLine(x.Equals(y)); // prints True 
Console.WriteLine(object.Equals(x, y)); // prints True

See this SharpLab demo for an example of both versions.

This means that the solution to your question is the following:

var props = typeof(User).GetProperties()
            .Where(pi => 
                !object.Equals(
                    pi.GetValue(AutomatedFromUser),
                    pi.PropertyType.GetDefault()
                ) 
             );

We use the static version to guard ourselves against null since null.Equals(...) would obviously throw. Calling the static method is the same as the instance method except that it first checks for reference equality and then guards against nulls. After that it calls x.Equals(y).

Upvotes: 2

Related Questions