【问题标题】:C# recursively check all values not nullC#递归检查所有不为空的值
【发布时间】:2020-02-17 15:59:13
【问题描述】:

不想重新发明轮子,是否有 .NET NuGet 库可以递归地对对象执行检查以进行参数检查?

如果不是,我将如何转换代码来检查一个属性是否为空,如果一个类型可以拥有自己的属性,递归检查该类型,并最终得到一个为空的属性名称列表。

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

【问题讨论】:

  • 考虑循环引用
  • 不知道为什么关闭,与其他问题完全不同
  • 收盘很荒谬。这不是同一个问题!是的,第二个问题“如何检查属性是否为空”是重复的,但主要问题是不同的!答案是:使用 [FluentValidation](docs.fluentvalidation.net/en/latest/built-in-validators.html) 语法如下:RuleFor(customer =&gt; customer.Surname).NotNull(); 这称为模型/对象验证。
  • 要检查每个成员的空值,你可以检查这个q/a:stackoverflow.com/questions/22683040/…
  • 首先我在这里看到代码味道,如果你想检查所有属性是否为空,为什么在第一种情况下你允许构造具有空属性的对象,是对象来了来自第三方 dll 吗? ,否则我强烈建议使用构建器来构建您的对象,根据我的经验,空检查使设计无效......

标签: c# .net reflection


【解决方案1】:

注意:您应该对构造函数参数或方法参数进行null检查,并在参数意外为null时抛出异常。最好遵循常见的最佳实践。

无论如何,这是一个示例,展示了如何使用扩展方法递归检查对象的所有属性并抛出查找空属性的异常...

您可以为对象创建一个扩展方法ThrowOnNullProperty 并像这样使用它:

something.ThrowOnNullProperty();

这是这种扩展方法的一个实现:

  1. 如果传递的对象为null,则抛出异常。
  2. 如果对象是原始类型或字符串,则继续。
  3. 如果该对象之前已访问过,则继续,否则将其添加到已访问对象列表中。
  4. 检查对象的第一级属性,如果有空属性,则抛出包含空属性名称的异常。
  5. 如果第一级属性不为 null,则每个属性的值都为 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);
    }
}

【讨论】:

    【解决方案2】:

    为此,编写一个方法来检查当前对象的属性并在非空属性上递归调用它。我继续编写了一些代码,其中包括遍历字典和枚举并检查它们是否为空,同时考虑到@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. 仅考虑公共属性,如果要考虑非公共属性,请使用BindingFlags
    2. 可能需要单独考虑某些类型(例如:字符串),也可能不需要(取决于您自己的情况)。
    3. 如前所述,代码循环字典和枚举,并检查它们的每个值。您可能想要也可能不想要(取决于您自己的情况)。

    【讨论】:

    • 我不会在生产代码中使用它。 GC 繁重且开销很大。有时间我会挑战这个答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-30
    • 1970-01-01
    • 2012-08-08
    相关资源
    最近更新 更多