testing某个属性是否可用于dynamicvariables

我的情况很简单。 在我的代码中的某处我有这个:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame(); //How to do this? if (myVariable.MyProperty.Exists) //Do stuff 

所以,基本上我的问题是如何检查(而不是抛出exception)我的dynamicvariables上有一个特定的属性。 我可以做GetType()但我宁愿避免,因为我不需要知道对象的types。 我真正想知道的是,是否有财产(或方法,如果这使生活更容易)可用。 任何指针?

我认为没有办法找出一个dynamicvariables是否有一个特定的成员而不尝试访问它,除非你重新实现了在C#编译器中处理dynamic绑定的方式。 这可能会包含很多猜测,因为它是根据C#规范的实现定义的。

所以你应该试着去访问这个成员,并且如果失败了,

 dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame(); try { var x = myVariable.MyProperty; // do stuff with x } catch (RuntimeBinderException) { // MyProperty doesn't exist } 

我以为我会比较Martijn的回答和svick的回答 …

以下程序返回以下结果:

 Testing with exception: 2430985 ticks Testing with reflection: 155570 ticks 

 void Main() { var random = new Random(Environment.TickCount); dynamic test = new Test(); var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000; i++) { TestWithException(test, FlipCoin(random)); } sw.Stop(); Console.WriteLine("Testing with exception: " + sw.ElapsedTicks.ToString() + " ticks"); sw.Restart(); for (int i = 0; i < 100000; i++) { TestWithReflection(test, FlipCoin(random)); } sw.Stop(); Console.WriteLine("Testing with reflection: " + sw.ElapsedTicks.ToString() + " ticks"); } class Test { public bool Exists { get { return true; } } } bool FlipCoin(Random random) { return random.Next(2) == 0; } bool TestWithException(dynamic d, bool useExisting) { try { bool result = useExisting ? d.Exists : d.DoesntExist; return true; } catch (Exception) { return false; } } bool TestWithReflection(dynamic d, bool useExisting) { Type type = d.GetType(); return type.GetProperties().Any(p => p.Name.Equals(useExisting ? "Exists" : "DoesntExist")); } 

所以我build议使用reflection。 见下文。


回应平淡的评论:

比率是reflection:exception 100000次迭代的reflection:exception

 Fails 1/1: - 1:43 ticks Fails 1/2: - 1:22 ticks Fails 1/3: - 1:14 ticks Fails 1/5: - 1:9 ticks Fails 1/7: - 1:7 ticks Fails 1/13: - 1:4 ticks Fails 1/17: - 1:3 ticks Fails 1/23: - 1:2 ticks ... Fails 1/43: - 1:2 ticks Fails 1/47: - 1:1 ticks 

…足够公平 – 如果你期望它失败的概率小于1/47,那就去例外。


上面假设你每次运行GetProperties() 。 您可以通过caching每个types的GetProperties()的结果来加快进程速度。 这可能会有所帮助,如果你反复检查相同的一组types。

也许使用reflection?

 dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame(); Type typeOfDynamic = myVar.GetType(); bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 

以防万一它帮助某人:

如果方法GetDataThatLooksVerySimilarButNotTheSame()返回一个ExpandoObject你也可以在检查之前ExpandoObject转换为IDictionary

 dynamic test = new System.Dynamic.ExpandoObject(); test.foo = "bar"; if (((IDictionary<string, object>)test).ContainsKey("foo")) { Console.WriteLine(test.foo); } 

那么,我面临类似的问题,但在unit testing。

使用SharpTestsEx你可以检查一个属性是否存在。 我使用这个testing我的控制器,因为由于JSON对象是dynamic的,有人可以改变名称,忘记改变它在JavaScript或东西,所以编写控制器时testing所有属性应该增加我的安全。

例:

 dynamic testedObject = new ExpandoObject(); testedObject.MyName = "I am a testing object"; 

现在,使用SharTestsEx:

 Executing.This(delegate {var unused = testedObject.MyName; }).Should().NotThrow(); Executing.This(delegate {var unused = testedObject.NotExistingProperty; }).Should().Throw(); 

使用这个,我使用“Should()。NotThrow()”testing所有现有的属性。

这可能不是主题,但可以为某人有用。

这两个常见的解决scheme包括进行调用和捕获RuntimeBinderException ,使用reflection检查调用,或序列RuntimeBinderException文本格式并从那里parsing。 exception的问题在于它们非常慢,因为当构造一个exception时,当前调用堆栈将被序列化。 序列化到JSON或类似的事情也会产生类似的惩罚。 这给我们留下了反思,但只有当底层对象实际上是一个拥有真实成员的POCO时才有效。 如果它是字典,COM对象或外部Web服务的dynamic包装,则reflection将无济于事。

另一个解决scheme是使用DynamicMetaObject来获取DLR看到的成员名称。 在下面的例子中,我使用静态类( Dynamic )来testingAge字段并显示它。

 class Program { static void Main() { dynamic x = new ExpandoObject(); x.Name = "Damian Powell"; x.Age = "21 (probably)"; if (Dynamic.HasMember(x, "Age")) { Console.WriteLine("Age={0}", x.Age); } } } public static class Dynamic { public static bool HasMember(object dynObj, string memberName) { return GetMemberNames(dynObj).Contains(memberName); } public static IEnumerable<string> GetMemberNames(object dynObj) { var metaObjProvider = dynObj as IDynamicMetaObjectProvider; if (null == metaObjProvider) throw new InvalidOperationException( "The supplied object must be a dynamic object " + "(ie it must implement IDynamicMetaObjectProvider)" ); var metaObj = metaObjProvider.GetMetaObject( Expression.Constant(metaObjProvider) ); var memberNames = metaObj.GetDynamicMemberNames(); return memberNames; } } 

丹尼斯的回答让我想到了使用JsonObjects的另一个解决scheme,

头部属性检查器:

 Predicate<object> hasHeader = jsonObject => ((JObject)jsonObject).OfType<JProperty>() .Any(prop => prop.Name == "header"); 

或者也许更好:

 Predicate<object> hasHeader = jsonObject => ((JObject)jsonObject).Property("header") != null; 

例如:

 dynamic json = JsonConvert.DeserializeObject(data); string header = hasHeader(json) ? json.header : null; 

对我来说这个工作:

 if (IsProperty(() => DynamicObject.MyProperty)) ; // do stuff delegate string GetValueDelegate(); private bool IsProperty(GetValueDelegate getValueMethod) { try { //we're not interesting in the return value. //What we need to know is whether an exception occurred or not var v = getValueMethod(); return (v == null) ? false : true; } catch (RuntimeBinderException) { return false; } catch { return true; } } 

如果你控制被dynamic使用的types,你不能为每个属性访问返回一个元组而不是一个元组吗? 就像是…

 public class DynamicValue<T> { internal DynamicValue(T value, bool exists) { Value = value; Exists = exists; } T Value { get; private set; } bool Exists { get; private set; } } 

可能是一个天真的实现,但是如果你每次都在内部构造一个这样的实例,而不是实际的值,那么你可以在每个属性访问上检查Exists ,然后如果值是default(T) (和不相关的)如果没有。

也就是说,我可能会错过一些关于dynamic如何工作的知识,这可能不是一个可行的build议。

继@karask的回答之后,你可以像下面这样把这个函数作为一个助手来包装:

 public static bool HasProperty(ExpandoObject expandoObj, string name) { return ((IDictionary<string, object>)expandoObj).ContainsKey(name); } 

这是另一种方式:

 using Newtonsoft.Json.Linq; internal class DymanicTest { public static string Json = @"{ ""AED"": 3.672825, ""AFN"": 56.982875, ""ALL"": 110.252599, ""AMD"": 408.222002, ""ANG"": 1.78704, ""AOA"": 98.192249, ""ARS"": 8.44469 }"; public static void Run() { dynamic dynamicObject = JObject.Parse(Json); foreach (JProperty variable in dynamicObject) { if (variable.Name == "AMD") { var value = variable.Value; } } } }