If you decide to go the generic way, without applying attributes on your properties, we could build further on Adriano Repetti's answer. My starting point is the IsNullOrEmpty
method, which is invoked against any property in the tree of properties of any specified complex object or list of properties of any specified simple object.
The goal is to test against default values and common empty values.
private static bool IsNullOrEmpty(object value){ if (Object.ReferenceEquals(value, null)) return true; var type = value.GetType(); return type.IsValueType&& Object.Equals(value, Activator.CreateInstance(type));}
By performing not just Object.ReferenceEquals
but also ==
, we can test both against references as overloaded operators. Nullable<>
has an overload that matches == null
when HasValue
is False
. Furthermore, we can check value
against the default instance of its type by calling ObjectFactory.CreateInstance
. Note that string
and DBNull
have custom empty values that do not correspond to Activator.CreateInstance
.
private static bool IsNullOrEmpty(object value) { if (Object.ReferenceEquals(value, null) || value == null) return true; // common Empty values if (value is string && string.IsNullOrEmpty((string)value)) { return true; } if (value is DBNull && (DBNull)value == DBNull.Value) { return true; } var type = value.GetType(); var typeDefault = ObjectFactory.CreateInstance(type); if (Object.ReferenceEquals(typeDefault, null) || typeDefault == null) return false; if (Object.ReferenceEquals(value, typeDefault) || value == typeDefault) return true; return false; }
Appendix: ObjectFactory
ObjectFactory
creates the default value of any type. Do not confuse this with the empty value. The latter is context-bound and should be specified on a case-by-case problem. By default, only string.Empty
and DBNull.Value
are considered empty.
public static class ObjectFactory { public static object CreateInstance(Type type) { if (TypeSystem.IsNullAssignable(type.NotNull())) { return null; } if (type.IsEnum) { return Enum.ToObject(type, Convert.ChangeType(0, Enum.GetUnderlyingType(type))); } if (type.IsValueType) { return Activator.CreateInstance(type); } return null; } }
Appendix: TypeSystem
TypeSystem
consists of convenience methods for verifying characteristics of a Type
. Only the relevant methods are included for brevity.
public static class TypeSystem { public static bool IsNullable(Type type) { return type.NotNull().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } public static bool IsNullAssignable(Type type) { if (IsNullable(type.NotNull()) || !type.IsValueType) { return true; } if (!type.IsGenericParameter) { return false; } #region Generic Parameter Handling // probably out-of-scope for this problem var gpa = type.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask; if (GenericParameterAttributes.None != (gpa & GenericParameterAttributes.ReferenceTypeConstraint)) { return true; } if (GenericParameterAttributes.None != (gpa & GenericParameterAttributes.NotNullableValueTypeConstraint)) { return false; } var gpc = type.GetGenericParameterConstraints(); if (gpc == null || !gpc.Any(x => x.IsClass && IsNullAssignable(x))) { return false; } #endregion return true; } }