C# рекурсивно проверяет все значения, не равные NULL - PullRequest
1 голос
/ 17 февраля 2020

Не желая заново изобретать колесо, есть ли библиотека. NET NuGet для рекурсивной проверки объекта на предмет проверки аргументов?

Если нет, то как мне преобразовать код для проверки, если свойство имеет значение null, и если тип может содержать свои собственные свойства, рекурсивно проверить этот тип и получить список имен свойств, которые имеют значение 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; }
}

Ответы [ 2 ]

1 голос
/ 23 февраля 2020

Примечание: Вы должны выполнить нулевую проверку параметров конструктора или параметров метода и выдать исключение, если параметр неожиданно равен нулю. Лучше следовать общепринятым лучшим практикам.

В любом случае, вот пример, показывающий, как вы можете рекурсивно проверить все свойства объекта, используя метод расширения, и сгенерировать исключение при поиске нулевых свойств ...

Вы можете создать метод расширения ThrowOnNullProperty для объекта и используйте его следующим образом:

something.ThrowOnNullProperty();

Вот реализация такого метода расширения:

  1. Если переданный объект имеет значение null, выдается исключение.
  2. Если объект является примитивным типом или строкой, продолжайте.
  3. Если объект был посещен ранее, затем продолжайте, в противном случае добавьте его в список посещенных объектов.
  4. Проверьте свойства объекта первого уровня и, если есть нулевые свойства, выведите исключение, содержащее имя нулевых свойств.
  5. Если свойства первого уровня не равны NULL, для каждого значения свойства go до 1.

Вот код:

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);
    }
}
1 голос
/ 23 февраля 2020

Для этого напишите метод, который проверяет свойства текущего объекта и рекурсивно вызывает его для ненулевых свойств. Я пошел дальше и написал некоторый код, который включает в себя циклический просмотр словарей и перечислимых элементов, а также проверку их на наличие нулей, принимая во внимание циклические ссылки, упомянутые в @ 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;
}

Я написал несколько классов для тестирования:

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; }
}

и протестировал его следующим образом:

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>())));

Вывод:

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

Несколько моментов, на которые следует обратить внимание:

  1. Рассматриваются только публичные c свойства, используйте BindingFlags, если вы хотите рассмотреть не публикуемые c свойства.
  2. Некоторые типы могут потребоваться для индивидуального рассмотрения (например, строка) или, возможно, нет ( в зависимости от вашего собственного случая).
  3. Как уже упоминалось ранее, код зацикливается на словарях и перечислимых элементах, а также проверяет каждое значение для них. Вы можете или не хотите этого (в зависимости от вашего собственного случая).
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...