如何检测属性是否存在于一个ExpandoObject?

在javascript中,你可以通过使用undefined关键字来检测属性是否被定义:

if( typeof data.myProperty == "undefined" ) ...

如何在c#中使用带有ExpandoObject的动态关键字而不抛出异常来做到这一点?

106882 次浏览

根据MSDN,声明表明它正在实现dictionary:

public sealed class ExpandoObject : IDynamicMetaObjectProvider,
IDictionary<string, Object>, ICollection<KeyValuePair<string, Object>>,
IEnumerable<KeyValuePair<string, Object>>, IEnumerable, INotifyPropertyChanged

你可以使用它来查看是否定义了一个成员:

var expandoObject = ...;
if(((IDictionary<String, object>)expandoObject).ContainsKey("SomeMember")) {
// expandoObject.SomeMember exists.
}

为什么不想使用反射来获取类型properyes?像这样

 dynamic v = new Foo();
Type t = v.GetType();
System.Reflection.PropertyInfo[] pInfo =  t.GetProperties();
if (Array.Find<System.Reflection.PropertyInfo>(pInfo, p => { return p.Name == "PropName"; }).    GetValue(v,  null) != null))
{
//PropName initialized
}

嘿,伙计们,不要什么都用反射,这会消耗大量的CPU周期。

下面是解决方案:

public class DynamicDictionary : DynamicObject
{
Dictionary<string, object> dictionary = new Dictionary<string, object>();


public int Count
{
get
{
return dictionary.Count;
}
}


public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string name = binder.Name;


if (!dictionary.TryGetValue(binder.Name, out result))
result = "undefined";


return true;
}


public override bool TrySetMember(SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
return true;
}
}

我最近回答了一个非常类似的问题:我如何反映在动态对象的成员?

简而言之,ExpandoObject不是您可能获得的唯一动态对象。反射适用于静态类型(不实现IDynamicMetaObjectProvider的类型)。对于实现此接口的类型,反射基本上是无用的。对于ExpandoObject,只需检查属性是否被定义为底层字典中的键。对于其他实现,这可能很有挑战性,有时唯一的方法是使用异常。有关详细信息,请点击上面的链接。

试试这个

public bool PropertyExist(object obj, string propertyName)
{
return obj.GetType().GetProperty(propertyName) != null;
}

UPDATED:如果动态对象属性存在,您可以使用委托并尝试从动态对象属性获取值。如果没有属性,则简单地捕获异常并返回false。

看一看,它对我来说很有效:

class Program
{
static void Main(string[] args)
{
dynamic userDynamic = new JsonUser();


Console.WriteLine(IsPropertyExist(() => userDynamic.first_name));
Console.WriteLine(IsPropertyExist(() => userDynamic.address));
Console.WriteLine(IsPropertyExist(() => userDynamic.last_name));
}


class JsonUser
{
public string first_name { get; set; }
public string address
{
get
{
throw new InvalidOperationException("Cannot read property value");
}
}
}


static bool IsPropertyExist(GetValueDelegate getValueMethod)
{
try
{
//we're not interesting in the return value. What we need to know is whether an exception occurred or not
getValueMethod();
return true;
}
catch (RuntimeBinderException)
{
// RuntimeBinderException occurred during accessing the property
// and it means there is no such property
return false;
}
catch
{
//property exists, but an exception occurred during getting of a value
return true;
}
}


delegate string GetValueDelegate();
}

代码的输出如下:

True
True
False

这里有一个重要的区别。

这里的大多数答案都是特定于问题中提到的ExpandoObject的。但是一个常见的用法(也是搜索时出现这个问题的原因)是在使用ASP。Net MVC ViewBag。这是DynamicObject的自定义实现/子类,当您检查任意属性名为空时,它不会抛出异常。假设你可以这样声明一个属性:

@{
ViewBag.EnableThinger = true;
}

然后假设你想检查它的值,以及它是否被设置——它是否存在。下面是有效的,将编译,不会抛出任何异常,并给你正确的答案:

if (ViewBag.EnableThinger != null && ViewBag.EnableThinger)
{
// Do some stuff when EnableThinger is true
}

现在去掉EnableThinger的声明。相同的代码编译和正常运行。没有必要反思。

与ViewBag不同的是,ExpandoObject会在你检查不存在的属性为null时抛出。为了从你的dynamic对象中获得MVC ViewBag的更温和的功能,你需要使用一个不抛出的dynamic实现。

你可以简单地使用MVC ViewBag中的确切实现:

. . .
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
result = ViewData[binder.Name];
// since ViewDataDictionary always returns a result even if the key does not exist, always return true
return true;
}
. . .

https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/DynamicViewDataDictionary.cs

你可以在MVC ViewPage中看到它被绑定到MVC视图中:

http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/ViewPage.cs

DynamicViewDataDictionary优雅行为的关键是在ViewDataDictionary上的Dictionary实现,这里:

public object this[string key]
{
get
{
object value;
_innerDictionary.TryGetValue(key, out value);
return value;
}
set { _innerDictionary[key] = value; }
}

https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/ViewDataDictionary.cs

换句话说,它总是为所有键返回一个值,不管其中有什么——当没有时它只是返回null。但是,viewdatdictionary有绑定到MVC模型的负担,所以最好剥离出优美的字典部分,以便在MVC视图之外使用。

它太长了,不能真正在这里发布所有的内容——大部分只是实现了字典——但这里有一个动态对象(类DDict),它不会对尚未声明的属性抛出null检查,在Github上:

https://github.com/b9chris/GracefulDynamicDictionary

如果你只是想通过NuGet将它添加到你的项目中,它的名字是GracefulDynamicDictionary

(authorDynamic as ExpandoObject).Any(pair => pair.Key == "YourProp");

我想创建一个扩展方法,这样我就可以做如下的事情:

dynamic myDynamicObject;
myDynamicObject.propertyName = "value";


if (myDynamicObject.HasProperty("propertyName"))
{
//...
}

... 但是你不能根据c# 5文档在ExpandoObject上创建扩展(更多信息在这里)。

所以我最终创建了一个类助手:

public static class ExpandoObjectHelper
{
public static bool HasProperty(ExpandoObject obj, string propertyName)
{
return obj != null && ((IDictionary<String, object>)obj).ContainsKey(propertyName);
}
}

使用它:

// If the 'MyProperty' property exists...
if (ExpandoObjectHelper.HasProperty(obj, "MyProperty"))
{
...
}

此扩展方法检查属性是否存在,然后返回值或null。如果您不希望应用程序抛出不必要的异常,这是很有用的,至少您可以帮助抛出这些异常。

    public static object Value(this ExpandoObject expando, string name)
{
var expandoDic = (IDictionary<string, object>)expando;
return expandoDic.ContainsKey(name) ? expandoDic[name] : null;
}

If可以这样使用:

  // lookup is type 'ExpandoObject'
object value = lookup.Value("MyProperty");

或者如果你的局部变量是“动态的”,你必须先将它转换为ExpandoObject。

  // lookup is type 'dynamic'
object value = ((ExpandoObject)lookup).Value("PropertyBeingTested");

根据你的用例,如果null可以被认为与undefined相同,你可以将你的ExpandoObject转换为DynamicJsonObject

    dynamic x = new System.Web.Helpers.DynamicJsonObject(new ExpandoObject());
x.a = 1;
x.b = 2.50;
Console.WriteLine("a is " + (x.a ?? "undefined"));
Console.WriteLine("b is " + (x.b ?? "undefined"));
Console.WriteLine("c is " + (x.c ?? "undefined"));

输出:

a is 1
b is 2.5
c is undefined