有没有一种通用方法可以递归检查反序列化对象是否存在空字段?

Is there a generic way to recursively check a deserialized object for null fields?

提问人:SRNissen 提问时间:4/11/2023 更新时间:4/11/2023 访问量:74

问:

我收到许多不同的消息,我将它们反序列化为非常不同的对象

这些对象中的大多数都具有不可为 null 的字段,但在所有消息类型(从 GraphQL、Azure 服务和存储总线,到 CosmosSB 以及我可能忘记的更多)中,其中一些对象很乐意反序列化为具有 null 字段的对象。

因此,我非常可以使用一个可以让我做的组件

T objectFromMessage = GetObject(message);
FieldAsserter.AssertAllFieldsSet<T>(objectFromMessage); //Throws if any non-nullable fields are null

您是否知道默认情况下或Nuget中的某个位置是否已经存在任何此类字段验证方法?或者,如果我必须自己写,你能给我任何线索吗?从哪里开始寻找?

你尝试了什么,你期待什么?

在这一点上,真的没什么 - 粗略搜索 NuGet 和一些关于反射的想法是我在思考“也许 SO 有答案”之前得到的。

SO 上已经有几个类似的问题,但它们似乎适用于单个对象 - 答案是“将扩展方法编写到您的类中”,但我想要一个适用于任何事情的通用方法 - 我有太多的消息类型。

C# 反射 NullReferenceException

评论

0赞 Radu Hatos 4/11/2023
我的建议是在每个模型的构造函数中抛出 ArgumentNullException 并验证属性。
0赞 MakePeaceGreatAgain 4/11/2023
“我有太多的消息类型” 好吧,这仍然不意味着您的函数必须适用于任何类型。您仍然可以提取所有常用功能的通用接口。你很确定不想检查是.DateTime.Nownull
0赞 JonasH 4/11/2023
为什么属性反序列化为 null?该属性是可选的吗?如果是这样,默认值应该是多少?如果它不是可选的,则首先不应为 null。
0赞 SRNissen 4/11/2023
@RaduHatos 是的,这也是我的方法,在允许这样做的语言中,但是,唉,在 C# 中,可以编写一个反序列化器来创建一个不可为 null 属性为 null 的对象,另请参阅有关可为 null 属性“真正含义”的讨论
1赞 Matthew Watson 4/11/2023
我假设您只想检查不可为空的属性?

答:

3赞 Felix Castor 4/11/2023 #1

您是否正在寻找像下面这样简单的东西?您还可以添加自定义或默认属性,以指示在此检查中忽略属性。

public static bool AreAllPropertiesNotNull<T>(this T obj)  where T : class
{

    PropertyInfo[] properties = typeof(T).GetProperties();

    foreach (PropertyInfo property in properties)
    {
        if (property.GetCustomAttribute<IgnoreAttribute>() != null)
        {
            continue; // Skip properties with Ignore attribute
        }

        dynamic propertyValue = property.GetValue(obj);

        if (propertyValue == null)
        {
            return false;
        }
    }

    return true;
}

使用扩展方法。

 T objectFromMessage = GetObject(message);
 Assert.True(objectFromMessage.AreAllPropertiesNotNull());

评论

0赞 SRNissen 4/11/2023
“您是否正在寻找像下面这样简单的东西”很有可能!我不擅长反思,如果不测试它,我就无法看到它是否能满足我的需要,但我会将其标记为可接受的答案并去尝试一下。(尽管我可能会用一条消息或异常来修改它,说明链条从齿轮上跳下来的位置,而不是布尔值)
2赞 Matthew Watson 4/11/2023 #2

或者,如果您只想检查不可为空的公共属性(例如 而不是 ),您可以过滤掉可为 null 的属性,并仅检查不可为 null 的属性,如下所示:stringstring?

(注意:这需要 .NET 6.0 或更高版本。

public static void RequiresPropertiesNotNull(object item, string name)
{
    if (item is null)
        throw new ArgumentNullException(nameof(item), "Item being checked cannot be null.");

    name ??= "<Unknown>"; // Don't want to throw if name is null, because it would hide the thing we're checking for nulls.

    var allProps = item.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy);

    var nonNullableProps =
        from prop in allProps
        where
            prop.CanRead
         && !prop.PropertyType.IsValueType
         && !IsNullable(prop)
        select prop;

    foreach (var prop in nonNullableProps)
    {
        object? value = prop.GetValue(item);

        if (value is null)
            throw new InvalidOperationException($"Property {prop.Name} for {name} of type {item.GetType().FullName} cannot be null.");
        
        RequiresPropertiesNotNull(value, name);
    }
}

public static bool IsNullable(PropertyInfo prop)
{
    var context = new NullabilityInfoContext();
    var info    = context.Create(prop);

    return info.WriteState is NullabilityState.Nullable;
}

请注意,这是递归的,因此它将检查所有公共属性和(递归)它们的所有公共属性,依此类推。

它不检查自引用对象,因此如果您尝试使用它来检查包含循环的对象图,您将收到堆栈溢出错误。

您可以按如下方式使用它:给定类和类,其中类包含允许为 null 的属性和不允许为 null 的属性:OuterInnerInnerCanBeNullInnerName

public sealed class Outer
{
    public string OuterName { get; set; }
    public Inner  Inner     { get; set; }
}

public sealed class Inner
{
    public string  InnerName { get; set; }
    public string? CanBeNull { get; set; }
}

然后在下面的代码中,第二次调用 将引发异常:RequiresPropertiesNotNull()

public static void Main()
{
    var innerOk  = new Inner { InnerName = "InnerOK" };
    var innerBad = new Inner();

    var outerOk  = new Outer { OuterName = "OuterOK",  Inner = innerOk  };
    var outerBad = new Outer { OuterName = "OuterBad", Inner = innerBad };

    RequiresPropertiesNotNull(outerOk,  nameof(outerOk));  // OK
    RequiresPropertiesNotNull(outerBad, nameof(outerBad)); // Throws
}

如果要删除递归部分,只需删除实现中的代码行即可。RequiresPropertiesNotNull(value, name);RequiresPropertiesNotNull()

评论

0赞 SRNissen 4/11/2023
我绝对希望它是递归的。