在C#中,我如何检查动态匿名类型上是否存在属性?

169

我有一个匿名类型对象,我从一个方法中以 dynamic 的形式接收它。我想检查该对象上是否存在某个属性。

....
var settings = new {
                   Filename="temp.txt",
                   Size=10
}
...

function void Settings(dynamic settings) {
var exists = IsSettingExist(settings,"Filename")
}

我该如何实现IsSettingExist?


2
可能是重复的问题:如何检测 ExpandoObject 上是否存在属性? - Sebastian
如果你发现自己严重依赖于动态对象,那么看看 F# 可能是值得的 - 顺便说一句,头像很不错。 - Piotr Kula
14个回答

198
  public static bool DoesPropertyExist(dynamic settings, string name)
  {
    if (settings is ExpandoObject)
      return ((IDictionary<string, object>)settings).ContainsKey(name);

    return settings.GetType().GetProperty(name) != null;
  }

  var settings = new {Filename = @"c:\temp\q.txt"};
  Console.WriteLine(DoesPropertyExist(settings, "Filename"));
  Console.WriteLine(DoesPropertyExist(settings, "Size"));

输出:

 True
 False

12
这在动态对象上不起作用,它总是返回 null。 - evilom
@evilom @Shikasta_Kashti 你们是否正在尝试在MVC的ViewBag中使用此方法?如果是,请参考https://dev59.com/ZnE85IYBdhLWcg3wUBsZ#24192518 - Ian Kemp
@Gaspa79。这是一种常见的编码约定。有些人喜欢在所有布尔属性上加上“Is”前缀。像这样的一致性可以防止您猜测标识符的前几个字符(之后,Intellisense会起作用),但代价是在这种情况下使英语变得有点尴尬。 - solublefish
3
我认为使用Is前缀的无效动词时态比使用HasProperty更加令人困惑。我还会说,在C♯中,使用这种语法不正确的前缀实际上是不符合惯用法的。 - Ben Collins
1
ExpandoObject不同于匿名类型。我对此是否有误? - ryanwebjackson
一些描述和链接可能会有所帮助。 - Norcino

48
public static bool HasProperty(dynamic obj, string name)
{
    Type objType = obj.GetType();

    if (objType == typeof(ExpandoObject))
    {
        return ((IDictionary<string, object>)obj).ContainsKey(name);
    }

    return objType.GetProperty(name) != null;
}

1
objType.GetProperty(name) != null; 在存在的属性上返回 null。 - Matas Vaitkevicius
6
objType.GetProperty(name) != null 总是会返回一个布尔值(即 bool 类型),根据定义,它永远不可能是 null - Alex McMillan
@AlexMcMillan 不确定你生活在哪个维度,一个不存在的属性使用 Type.GetProperty(string) 返回的除了 null 以外还有其他值。 - Ian Kemp
4
@IanKemp,AlexMcMillan实际上是在回复MatasVaitkevicius的评论时说objType.GetProperty(name) != null - Serg

21

这适用于匿名类型、ExpandoObjectNancy.DynamicDictionary或任何可以转换为IDictionary<string, object>的东西。

    public static bool PropertyExists(dynamic obj, string name) {
        if (obj == null) return false;
        if (obj is IDictionary<string, object> dict) {
            return dict.ContainsKey(name);
        }
        return obj.GetType().GetProperty(name) != null;
    }

2
很棒的解决方案。在将JSON字符串转换为JObject时,我需要添加一个IF语句……“if (obj is Newtonsoft.Json.Linq.JObject) return ((Newtonsoft.Json.Linq.JObject)obj).ContainsKey(name);” - rr789
1
这个方法对我也起作用了。Seth Reno的回答非常好。我还按照rr789的建议在上面的函数中添加了 "if (obj is Newtonsoft.Json.Linq.JObject) return ((Newtonsoft.Json.Linq.JObject)obj).ContainsKey(name);"。所以请您也编辑一下您的回答,将它包含进去。 - Brijesh Kumar Tripathi
1
谢谢@BrijeshKumarTripathi!这正是我的情况。 - ryanwebjackson

15

如果你能控制创建/传递设置对象,我建议使用 ExpandoObject 代替。

dynamic settings = new ExpandoObject();
settings.Filename = "asdf.txt";
settings.Size = 10;
...

function void Settings(dynamic settings)
{
    if ( ((IDictionary<string, object>)settings).ContainsKey("Filename") )
        .... do something ....
}

我无法更改它,我能将其转换为ExpendoObject吗? - David MZ

10
我遇到的动态类型是Newtonsoft.Json.Linq.JObject而不是IDictionary
我添加了额外的if语句,现在正常工作。
public static bool PropertyExists(dynamic obj, string name)
{
    if (obj == null) return false;

    else if (obj is IDictionary<string, object> dict)
    {
        return dict.ContainsKey(name);
    }

    else if (obj is Newtonsoft.Json.Linq.JObject jObject)
    {
        return jObject.ContainsKey(name);
    }

    else
    {
        return obj.GetType().GetProperty(name) != null;
    }
}

编辑:使用开关表达式的比以前更智能的解决方案。

public static bool PropertyExists(dynamic obj, string name)
{
    if (obj == null) return false;
    return obj switch
    {
        IDictionary<string, object> dict => dict.ContainsKey(name),
        Newtonsoft.Json.Linq.JObject jObject => jObject.ContainsKey(name),
        _ => obj.GetType().GetProperty(name) != null
    };
}

7

如果有人需要处理来自Json的动态对象,我已经修改了Seth Reno的答案以处理从NewtonSoft.Json.JObjcet反序列化的动态对象。

public static bool PropertyExists(dynamic obj, string name)
    {
        if (obj == null) return false;
        if (obj is ExpandoObject)
            return ((IDictionary<string, object>)obj).ContainsKey(name);
        if (obj is IDictionary<string, object> dict1)
            return dict1.ContainsKey(name);
        if (obj is IDictionary<string, JToken> dict2)
            return dict2.ContainsKey(name);
        return obj.GetType().GetProperty(name) != null;
    }

5

将Serj-TM和user3359453的答案合并并修复,以使其适用于ExpandoObject和DynamicJsonObject。这对我有效。

public static bool HasPropertyExist(dynamic settings, string name)
{
    if (settings is System.Dynamic.ExpandoObject)
        return ((IDictionary<string, object>)settings).ContainsKey(name);

    if (settings is System.Web.Helpers.DynamicJsonObject)
    try
    {
        return settings[name] != null;
    }
    catch (KeyNotFoundException)
    {
        return false;
    }


    return settings.GetType().GetProperty(name) != null;
}

3
这对我有效:
  public static bool IsPropertyExist(dynamic dynamicObj, string property)
       {
           try
           {
               var value=dynamicObj[property].Value;
               return true;
           }
           catch (RuntimeBinderException)
           {

               return false;
           }

       }

20
允许异常发生并捕获它们并不是首选解决方案,因为抛出和捕获异常会涉及大量开销。这只是最后的一招。异常是为设计时不应在执行过程中发生的情况而设计的,比如网络不可用。这里有更好的解决方案。 - Whatever Man
当值实际存在时,会出现RuntimeBinderExceptiondynamicObj[property].Value失败的情况... 只需使用var value = dynamicObj[property]即可... 当它不存在时,将在Dictionary上抛出KeyNotFoundException... 请参见下文... - Matas Vaitkevicius
1
在业务逻辑中使用异常并不是可接受的解决方案。一年级,第二学期。 - Artem A

3

扩展@Kuroro的答案,如果您需要测试属性是否为空,则以下内容应该有效。

public static bool PropertyExistsAndIsNotNull(dynamic obj, string name)
{
    if (obj == null) return false;
    if (obj is ExpandoObject)
    {
        if (((IDictionary<string, object>)obj).ContainsKey(name))
            return ((IDictionary<string, object>)obj)[name] != null;
        return false;
    }
    if (obj is IDictionary<string, object> dict1)
    {
        if (dict1.ContainsKey(name))
            return dict1[name] != null;
        return false;
    }
    if (obj is IDictionary<string, JToken> dict2)
    {
        if (dict2.ContainsKey(name))
            return (dict2[name].Type != JTokenType.Null && dict2[name].Type != JTokenType.Undefined);
        return false;
    }
    if (obj.GetType().GetProperty(name) != null)
        return obj.GetType().GetProperty(name).GetValue(obj) != null;
    return false;
}

3
使用反射,这是我使用的函数:
public static bool doesPropertyExist(dynamic obj, string property)
{
    return ((Type)obj.GetType()).GetProperties().Where(p => p.Name.Equals(property)).Any();
}

然后...
if (doesPropertyExist(myDynamicObject, "myProperty")){
    // ...
}

3
GetProperties() 方法无法列出 DynamicObject 上的动态成员。对于此,有一个专门的函数 GetDynamicMemberNames()。 - Marco Guignard
1
使用lambda表达式先使用Where,然后再使用Any是多余的,因为你可以在Any中制定筛选表达式。 - pholpar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接