2

Without wanting to reinvent the wheel, is there a .NET NuGet library to perform checks on a object recursively for argument checking?

If not, how would I convert the code to check if a property is null, and if a type that can hold properties of its own, recursively check that type, and end up with a list of property names that are null.

public static class Assert
{
    public static void AllPropertiesNotNull<T>(T obj)
    {
        if (obj == null)
            throw new ArgumentNullException(nameof(obj));

        var emptyProperties = typeof(T)
                                .GetProperties()
                                .Select(prop => new { Prop = prop, Val = prop.GetValue(obj, null) })
                                .Where(val => IsEmpty((dynamic)val.Val))
                                .Select(val => val.Prop.Name)
                                .ToList();

        if (emptyProperties.Count > 0)
            throw new ArgumentNullException(emptyProperties.First());
    }

    private static bool IsEmpty(object o) { return o == null; }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
morleyc
  • 2,169
  • 10
  • 48
  • 108
  • 4
    Take into account circular references – dcg Feb 17 '20 at 16:03
  • havent a clue why this was closed, totally different to the other question – morleyc Feb 17 '20 at 16:08
  • 1
    The closing is ridiculous. It is not the same question! Yes, the 2nd question "how to check if a property is null" is a duplicate but the main question is different! And the answer for it is to: Use [FluentValidation](https://docs.fluentvalidation.net/en/latest/built-in-validators.html ) The syntax looks like: `RuleFor(customer => customer.Surname).NotNull();` this called modell/object validation. – Alb Feb 17 '20 at 16:09
  • To check null values for each members, you can check this q/a : https://stackoverflow.com/questions/22683040/how-to-check-all-properties-of-an-object-whether-null-or-empty – Milan Raval Feb 17 '20 at 18:37
  • 1
    Firstly i see a code smell here , if you want to check if all the properties or null or not , why in the first case you allowed the construction of the object with null properties , is the object coming from a third party dll ? , or else i would strongly suggest to use a builder for the construction of your object , in my experience the null checks have nullified the designs.... – Keshav Raghav Feb 23 '20 at 12:51
  • Very good comment @KeshavRaghav it is being used specifically to chain interfaces together, much like a software driver. I have a lot of interfaces and want to ensure they are all passed in with valid properties set as we will be using them to callback the upper layer (and this upper layer i will not be writing) – morleyc Feb 23 '20 at 12:52
  • i dont think you can get some nuget library for this , i sometimes use code contracts library for preconditions , i am not sure if you want to do that , or else how about an exetension method like this : – Keshav Raghav Feb 23 '20 at 13:01
  • public static class ValidationExtensions { public static bool AllPropertiesValid(this object obj) { if (obj == null) return false; return obj.GetType().GetProperties().All(p => { var attrib = p.GetCustomAttributes(typeof(ValidatorAttribute), true) .FirstOrDefault() as ValidatorAttribute; if (attrib == null) attrib = new DefaultValidatorAttribute(); return attrib.Validate(p.GetValue(obj)); }); } } – Keshav Raghav Feb 23 '20 at 13:01
  • @g18c, What kind of object do you have? Is it some JSON? Or something that can be easily converted to JSON? – Just Shadow Feb 23 '20 at 15:04
  • @just shadow - it will be an interface with delegate properties – morleyc Feb 23 '20 at 19:32
  • It's better to not perform such check on your objects. You should add null checking on constructor parameters or parameters and throw exception when the parameter is unexpectedly null. – Reza Aghaei Feb 23 '20 at 21:02
  • You sure you want to check for null instead for default? – vaiperboy Feb 27 '20 at 21:39

2 Answers2

1

To do so, write a method to check the properties of current object and call it recursively on the non-null properties. I went ahead and wrote some code, which includes looping over dictionaries and enumerables and checking them for nulls also, taking into account circular references as mentioned by @dcg.

static readonly HashSet<Type> excludedTypes = new HashSet<Type>{ typeof(string) };

public static List<string> AllPropertiesNotNull(IDictionary dictionary, string name, HashSet<object> alreadyChecked)
{
  List<string> nullValues = new List<string>();

  foreach(object key in dictionary.Keys)
  {
    object obj = dictionary[key];

    if (!alreadyChecked.Contains(obj))
    {
      string elementName = $"{name}[\"{key}\"]";
      nullValues.AddRange(AllPropertiesNotNull(obj, elementName, alreadyChecked));
    }
  }

  return nullValues;
}

public static List<string> AllPropertiesNotNull(IEnumerable enumerable, string name, HashSet<object> alreadyChecked)
{
  List<string> nullValues = new List<string>();
  int i = 0;

  foreach (object obj in enumerable)
  {
    if (!alreadyChecked.Contains(obj))
    {
      string elementName = $"{name}[{i}]";
      nullValues.AddRange(AllPropertiesNotNull(obj, elementName, alreadyChecked));
    }

    i++;
  }

  return nullValues;
}

public static List<string> AllPropertiesNotNull(object obj, string name, HashSet<object> alreadyChecked, string baseName = "")
{
  List<string> nullValues = new List<string>();
  string basePropertyName;

  if (string.IsNullOrEmpty(baseName))
  {
    basePropertyName = name;
  }
  else
  {
    basePropertyName = baseName + "." + name;
  }

  if (obj == null)
  {
    nullValues.Add(basePropertyName);
  }
  else if (!alreadyChecked.Contains(obj))
  {
    alreadyChecked.Add(obj);

    if (!excludedTypes.Contains(obj.GetType()))
    {
      foreach (PropertyInfo property in obj.GetType().GetProperties())
      {
        object value = property.GetValue(obj);
        string propertyName = basePropertyName + "." + property.Name;

        if (value == null)
        {
          nullValues.Add(propertyName);
        }
        else
        {
          if (typeof(IDictionary).IsAssignableFrom(property.PropertyType))
          {
            nullValues.AddRange(AllPropertiesNotNull((IDictionary)value, propertyName, alreadyChecked));
          }
          else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
          {
            nullValues.AddRange(AllPropertiesNotNull((IEnumerable)value, propertyName, alreadyChecked));
          }
          else
          {
            nullValues.AddRange(AllPropertiesNotNull(value, property.Name, alreadyChecked, basePropertyName));
          }
        }
      }
    }
  }

  return nullValues;
}

I wrote some classes to test with:

class A
{
  public string s1 { set; get; }
  public string s2 { set; get; }
  public int i1 { set; get; }
  public int? i2 { set; get; }
  public B b1 { set; get; }
  public B b2 { set; get; }
}

class B
{
  public string s1 { set; get; }
  public string s2 { set; get; }
  public int i1 { set; get; }
  public int? i2 { set; get; }
  public A a1 { set; get; }
  public Dictionary<int, string> d1 { set; get; }
  public List<A> l1 { set; get; }
}

and tested it as follows:

A a = new A
{
  s1 = "someText"
};
B b = new B
{
  s1 = "someText",
  a1 = a,
  d1 = new Dictionary<int, string>
  {
    { 1, "someText" },
    { 2, null }
  },
  l1 = new List<A>{ null, new A { s1 = "someText" } , a }
};
a.b1 = b;
Console.WriteLine(string.Join("\n", AllPropertiesNotNull(a, nameof(a), new HashSet<object>())));

Output:

a.s2
a.i2
a.b1.s2
a.b1.i2
a.b1.d1["2"]
a.b1.l1[0]
a.b1.l1[1].s2
a.b1.l1[1].i2
a.b1.l1[1].b1
a.b1.l1[1].b2
a.b2

Few points to take note of:

  1. Only public properties are considered, use BindingFlags if you want to consider non-public ones.
  2. Some types might need to be individually considered (e.g.: string) or maybe not (depending on your own case).
  3. As mentioned before, the code loops on dictionaries and enumerables and checks every value for them too. You might or might not want that (depending on your own case).
Sohaib Jundi
  • 1,576
  • 2
  • 7
  • 15
  • I wouldn't use this in production code. GC heavy and lots of overhead. I will challenge this answer once I get time. – l33t Feb 28 '20 at 03:16
1

Note: You should do null checking on constructor parameters or method parameters and throw exception when the parameter is unexpectedly null. It's better to follow the common best practices.

Anyway, here is an example showing how you can check all properties of an object recursively using an extension method and throw exception of finding null properties...

You can create an extension method ThrowOnNullProperty for object and use it like this:

something.ThrowOnNullProperty();

Here is an implementation of such extension method:

  1. If the passed object is null, throw exception.
  2. If the object is a primitive type or a string, continue.
  3. If the object has been visited before, then continue, otherwise add it to list of visited objects.
  4. Check first level properties of the object and if there are null properties, throw an exception containing name of the null properties.
  5. If the first level properties are not null, the for each property value go to 1.

Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
public static class ObjectExtensions
{
    public static void ThrowOnNullProperty(this object obj)
    {
        ThrowOnNullProperty(obj, new HashSet<object>());
    }
    private static void ThrowOnNullProperty(object obj, HashSet<object> visitedObjects)
    {
        if (obj == null)
            throw new ArgumentNullException(nameof(obj));
        if (obj.GetType().IsPrimitive || obj.GetType() == typeof(string))
            return;
        if (visitedObjects.Contains(obj))
            return;
        visitedObjects.Add(obj);

        var nullPropertyNames = obj.GetType().GetProperties()
           .Where(p => p.GetValue(obj) == null)
           .Select(p => p.Name);
        if (nullPropertyNames.Any())
            throw new ArgumentException(
                $"Null properties: {string.Join(",", nullPropertyNames)}");

        var notNullPropertyValues = obj.GetType().GetProperties()
            .Select(p => p.GetValue(obj))
            .Where(v => v != null);
        foreach (var item in notNullPropertyValues)
            ThrowOnNullProperty(item, visitedObjects);
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398