There are several problems with the answers here, most notably that the original question doesn't seem to be answered, namely, that we want to create a clone a class with ONLY the fields that are set to default/NULL. But lets put a pin in that for a moment.
Lets break this problem down into three parts:
- We need a way to specify which properties we want to validate (and we should be able to overlap that with multiple business requirements)
- We need a way to generically walk the class recursively to find all fields that are either NULL or set to their default value for all types.
- We need to construct some class as a return type that reflects all of those fields that are NULL/default.
Fields to be Validated
The first requirement is fairly straight forward, as .NET has been offering this for quite some time. We can use property decorators, which will allow use to use reflection to walk the properties when we move on to step (2). We achieve this using the following code (for this example, I am choosing to use the decorator name 'Validate', but you can really use anything you like):
[AttributeUsage(AttributeTargets.Property)]public class ValidateAttribute : Attribute{ /// <summary> /// Name /// </summary> public string Validate { get; set; }}
The beauty of decorators and reflection is that they are both self commenting, and can be mixed and matched. Supposed you want to validate one set of properties for new records, but another set only for update. You could create two decorators as follows:
[AttributeUsage(AttributeTargets.Property)]public class NewValidateAttribute : Attribute{ /// <summary> /// Name /// </summary> public string Validate { get; set; }}[AttributeUsage(AttributeTargets.Property)]public class UpdateValidateAttribute : Attribute{ /// <summary> /// Name /// </summary> public string Validate { get; set; }}
Your class could then be implemented as follows:
public class Foo{ [NewValidate] public string Name { get; set; } [NewValidate] [UpdateValidate] public string Age { get; set; } [UpdateValidate] public bool LikesIceCream { get; set; }}
Validation
The next task is the actual validation. Here is where things should be done in a way that should support the following idioms:
- Should be able to handle POD (plain old data) types (both value and reference types)
- Should be able to handle user types
- Should be able to handle Generic (e.g. ICollection, etc.)
- Should be able to handle nested classes to n-levels
- Should be able to handle any decorator type 'T'
- Should follow separation of concerns pattern to support unit testing
If we separate out these requirements, we can conclude that this really boils down to:
- Walk the target object looking for all public properties with a decorator of type 'T'
- Find the value of that property and decide if we need to recurs (go back to step 1)
- If its a reference type, do the check, make a note and move on
- If its a value type, create an instance to get its default, compare, do the check, make a note, and move on
- If its a generic, same this, etc.
Let's first walk the object:
/// <summary> /// Iterates through all properties in the object 'obj' with the attribute 'T' and compiles a list of property /// names whose value is the default value (i.e. the value assigned when the property is fixed created). /// </summary> /// <param name="obj">The object whose properties we will iterate</param> /// <returns>a list of property names as strings</returns> /// <remarks> /// This method works on the assumption that default values for properties are never valid values. For example, an /// integer property cannot contain '0' as a valid value. /// </remarks> public static List<string> FindAllPropertiesWithDefaultValues<T>(object obj) { // collection of properties that contain default values var defaultProps = new List<string>(); // Find all properties (using reflection) that contain an attribute of type 'type' var props = obj.GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(T))); props.ToList().ForEach(prop => { // get the value and default value of 'prop' var val = prop.GetValue(obj); var defVal = Factory.GetDefaultValueForType(prop.PropertyType); // If the property contains a default value (such as what happens when an object is first created without setting any // values), is null or is an empty generic collection, add it to the defaultProps collection. // In the case of generic values, this test will fail which is not an issue since we cant test generic properties anyways. if (val == null || val.Equals(defVal) || IsEmptyGenericCollection(prop, obj)) { var thisType = prop.DeclaringType?.Name ?? "<Unknown>"; defaultProps.Add($"{thisType}:{prop.Name}"); } // Don't step into user type if the type itself is null if (prop.GetValue(obj) != null) { // Make recursive call to this function defaultProps.AddRange(FindAllPropertiesWithDefaultValues<T>(prop.GetValue(obj))); } }); // Check if all of the validators returned success and return the result of that check return defaultProps; }
This method returns a collection of 'classname':'property' strings for all properties that are found to be either NULL or default.
Lets talk about two methods here for a moment. The IsEmptyGenericCollection
method checks to see if the object has the IsGenericType
property set, or if the object is a descendant of IEnumerable
. In any case, this is where the special tests are then made to determine if the collection object is empty, since the presence of a fully constructed container is not enough evidence of a 'default' object -- we need to know the presence/absence of elements to make that determination.
/// <summary> /// Test to see if the property of the object 'obj' is a generic collection, and if so, test to see if that collection /// has any elements.Is /// </summary> /// <param name="prop">The property of 'obj' that we are testing</param> /// <param name="obj">The object that owns the property 'prop'</param> /// <returns>true is the property is a generic collection with no elements, false otherwise</returns> private static bool IsEmptyGenericCollection(PropertyInfo prop, object obj) { // sanity if (prop == null) { throw new ArgumentNullException(nameof(prop)); } // Check to see if this is a generic collection. All generic collections are derived from IEnumerable as well // as IEnumerable<T>. if (!prop.PropertyType.IsGenericType || !(prop.GetValue(obj) is System.Collections.IEnumerable)) { return false; } // retrieve the enumerator and check to see if there is an element to advance to... if not, then we // are in the default state var enumerable = prop.GetValue(obj) as System.Collections.IEnumerable; if (enumerable == null) { return false; } var enumerator = enumerable.GetEnumerator(); var isEmptyGenericCollection = !enumerator.MoveNext(); return isEmptyGenericCollection; }
The second method needed is the instance creator for value types. I chose to move that off to a separate class because thats how I partitioned things for testing in my projects.
public static class Factory{ /// <summary> /// Create a default object of a given type /// </summary> /// <param name="type">The type to create</param> /// <returns>The type in its default state</returns> public static object GetDefaultValueForType(Type type) { return type.IsValueType ? Activator.CreateInstance(type) : null; }}
Constructing a Class
Before we tackle this last point, and to my mind was what the questioner was asking in the first place, lets create a test case.
Lets first make some test classes as follows:
public class ExampleClassB{ [Validate] public double SomeDouble { get; set; } [Validate] public Rectangle SomeRectangle { get; set; }}public class ExampleClassA{ /// <summary> /// Ctor /// </summary> public ExampleClassA() { someList = new List<string>(); SomeClassB_1 = new ExampleClassB(); } [Validate] public string SomeString { get; set; } [Validate] public int SomeInt { get; set; } [Validate] public TimeSpan SomeTimeSpan { get; set; } [Validate] public Guid SomeGuid { get; set; } private List<string> someList; [Validate] public List<string> SomeList => someList; [Validate] public ExampleClassB SomeClassB_1 { get; set; } [Validate] public ExampleClassB SomeClassB_2 { get; set; } public string WontBeValidated { get; set; }}
This test will test the following:
- POD types for both reference types and value types (I chose Guid, TimeSpan, DateTime as well as some classics like int and double as well as Rectangle for you graphics types out there)
- IList for testing generics
- Recursive testing of nested classes, one that is instantiated, one that remains dangling (NULL)
We test this as follows in a console app:
/// <summary>/// Main program/// </summary>internal static class Program{ /// <summary> /// Main entry point /// </summary> /// <param name="args"></param> static void Main(string[] args) { var exampleClass = new ExampleClassA(); var results = Validate.FindAllPropertiesWithDefaultValues<ValidateAttribute>(exampleClass); results.ForEach(Console.WriteLine); Console.ReadLine(); }}
Our output looks as follows:
So this begs the question... why would you want to return a cloned class with only the properties present that are NULL default? Is that really your intent?
Some possibilities...
A collection (List? Set/Hash? Dictionary? etc) of classes/properties that are NULL/default? It could look something like this:
public class DefaultProp{ public string Class { get; set; } public string Property { get; set; } public string Type { get; set; }}var PropList = new List<DefaultProp>();
You could then do whatever you need to with this information.
Just my two shekels...