【问题标题】:Test if a property is available on a dynamic variable测试属性是否可用于动态变量
【发布时间】:2011-03-01 05:57:53
【问题描述】:

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

//How to do this?
if (myVariable.MyProperty.Exists)   
//Do stuff

所以,基本上我的问题是如何检查(不抛出异常)某个属性在我的动态变量上是否可用。我可以做GetType(),但我宁愿避免这种情况,因为我真的不需要知道对象的类型。我真正想知道的是一个属性(或方法,如果这让生活更轻松的话)是否可用。有什么指点吗?

【问题讨论】:

标签: c# dynamic dynamic-keyword


【解决方案1】:

我认为没有办法在不尝试访问它的情况下找出dynamic 变量是否具有某个成员,除非您重新实现了在 C# 编译器中处理动态绑定的方式。根据 C# 规范,这可能包括很多猜测,因为它是实现定义的。

因此,如果失败,您实际上应该尝试访问该成员并捕获异常:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

【讨论】:

  • 我会将此标记为答案,因为它已经很久了,它似乎是最好的答案
  • 更好的解决方案 - stackoverflow.com/questions/2839598/…
  • @ministrymason 如果你的意思是投射到IDictionary 并使用它,那只适用于ExpandoObject,它不适用于任何其他dynamic 对象。
  • RuntimeBinderExceptionMicrosoft.CSharp.RuntimeBinder 命名空间中。
  • 我仍然觉得使用 try/catch 而不是 if/else 通常是不好的做法,无论这种情况的具体情况如何。
【解决方案2】:

也许使用反射?

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

【讨论】:

  • 引用问题“。我可以做 GetType() 但我宁愿避免这样做”
  • 这不是和我的建议有同样的缺点吗? RouteValueDictionary uses reflection to get properties.
  • 你可以不用Where.Any(p => p.Name.Equals("PropertyName"))
  • Please see my answer 用于答案比较。
  • 单行:((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName"))。要使编译器对 lambda 感到满意,需要进行类型转换。
【解决方案3】:

如果您控制用作动态的类型,您不能为每个属性访问返回一个元组而不是一个值吗?比如……

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,然后点击Value,如果它的值为@ 987654324@(不相关)如果不是。

也就是说,我可能缺少一些关于动态如何工作的知识,这可能不是一个可行的建议。

【讨论】:

    【解决方案4】:

    嗯,我遇到了类似的问题,但在单元测试中。

    使用 SharpTestsEx 您可以检查属性是否存在。我用这个来测试我的控制器,因为 JSON 对象是动态的,有人可以更改名称而忘记在 javascript 或其他东西中更改它,因此在编写控制器时测试所有属性应该会增加我的安全性。

    例子:

    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()”测试所有现有属性。

    这可能超出主题,但对某人有用。

    【讨论】:

    • 谢谢,非常有用。使用 SharpTestsEx 我还使用以下行来测试动态属性的值:((string)(testedObject.MyName)).Should().Be("I am a testing object");
    【解决方案5】:

    我想我会比较 Martijn's answersvick's answer...

    以下程序返回以下结果:

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

    As a result I'd suggest using reflection.见下文。


    回应平淡的评论:

    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()。您可以通过缓存GetProperties() 为字典或类似文件中的每种类型的结果来加快该过程。如果您一遍又一遍地检查同一组类型,这可能会有所帮助。

    【讨论】:

    • 我同意并喜欢在我的工作中进行适当的反思。它对 Try/Catch 的好处仅在于抛出异常时。那么在这里使用反射之前有人应该问什么 - 它可能是某种方式吗? 90% 甚至 75% 的时间,你的代码会通过吗?那么 Try/Catch 仍然是最优的。如果它悬而未决,或者选择太多而不太可能,那么你的反思就在眼前。
    • @bland 已编辑答案。
    • 谢谢,现在看起来真的很完整了。
    • @dav_i 比较两者是不公平的,因为两者的行为不同。 svick 的回答更完整。
    • @dav_i 不,它们不执行相同的功能。 Martijn 的回答检查 C# 中的常规编译时类型 上是否存在属性,即声明的动态(意味着它忽略编译时安全检查)。而 svick 的答案是检查 真正动态 对象上是否存在属性,即实现 IIDynamicMetaObjectProvider 的东西。我确实理解您回答背后的动机,并对此表示感谢。这样回答是公平的。
    【解决方案6】:

    对我来说这是可行的:

    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;
        }
        catch (RuntimeBinderException)
        {
            return false;
        }
        catch
        {
            return true;
        }
    }
    

    【讨论】:

    • null 不代表该属性不存在
    • 我知道,但如果它为空,我不需要对这个值做任何事情,因此对于我的用例来说没问题
    【解决方案7】:

    这是另一种方式:

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

    【讨论】:

    • 您从哪里得知问题是关于测试 JObject 的属性?您的答案仅限于在其属性上公开 IEnumerable 的对象/类。 dynamic 不保证。 dynamic 关键字是更多更广泛的主题。去检查你是否可以像这样在dynamic foo = new List&lt;int&gt;{ 1,2,3,4 }中测试Count
    【解决方案8】:

    以防万一它对某人有所帮助:

    如果方法GetDataThatLooksVerySimilarButNotTheSame() 返回ExpandoObject,您也可以在检查之前强制转换为IDictionary

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

    【讨论】:

    • 不知道为什么这个答案没有更多的选票,因为它完全符合要求(没有异常抛出或反射)。
    • @Wolfshead 如果您知道您的动态对象是 ExpandoObject 或其他实现 IDictionary 但如果它碰巧是其他东西,那么这将失败。
    【解决方案9】:

    Denis 的回答让我想到了使用 JsonObjects 的另一种解决方案,

    标题属性检查器:

    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;
    

    【讨论】:

    • 请问有没有机会知道这个答案有什么问题?
    • 不知道为什么这被否决了,对我来说效果很好。我将每个属性的 Predicate 移动到一个辅助类中,并调用 Invoke 方法从每个属性中返回一个布尔值。
    【解决方案10】:

    根据@karask 的回答,您可以像这样将函数包装为助手:

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

    【讨论】:

      【解决方案11】:

      对此的两种常见解决方案包括拨打电话并捕获RuntimeBinderException,使用反射检查调用,或序列化为文本格式并从那里解析。异常的问题是它们非常慢,因为当构造一个异常时,当前调用堆栈是序列化的。序列化为 JSON 或类似的东西会产生类似的惩罚。这给我们留下了反射,但它只有在底层对象实际上是一个带有真实成员的 POCO 时才有效。如果它是字典、COM 对象或外部 Web 服务的动态包装器,那么反射将无济于事。

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

      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 " +
                  "(i.e. it must implement IDynamicMetaObjectProvider)"
              );
      
              var metaObj = metaObjProvider.GetMetaObject(
                  Expression.Constant(metaObjProvider)
              );
      
              var memberNames = metaObj.GetDynamicMemberNames();
      
              return memberNames;
          }
      }
      

      【讨论】:

      • 原来Dynamitey nuget 包已经做到了这一点。 (nuget.org/packages/Dynamitey)
      • 不工作,在 VS2022 上测试,GetMembersName 返回具有许多属性的空解析对象
      【解决方案12】:

      在我的例子中,我需要检查是否存在具有特定名称的方法,所以我为此使用了一个接口

      var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(pluginName) as dynamic;
      if (plugin != null && plugin is ICustomPluginAction)
      {
          plugin.CustomPluginAction(action);
      }
      

      此外,接口可以包含的不仅仅是方法:

      接口可以包含方法、属性、事件、索引器或任何 这四种成员类型的组合。

      发件人:Interfaces (C# Programming Guide)

      优雅,无需捕获异常或玩反射......

      【讨论】:

        【解决方案13】:

        我知道这是很老的帖子,但这里有一个简单的解决方案,可以使用dynamic 输入c#

        1. 可以使用简单的反射来枚举直接属性
        2. 或者可以使用object扩展方法
        3. 或使用GetAsOrDefault&lt;int&gt; 方法获取一个新的强类型对象,如果存在则带有值,如果不存在则带有默认值。
        public static class DynamicHelper
        {
            private static void Test( )
            {
                dynamic myobj = new
                                {
                                    myInt = 1,
                                    myArray = new[ ]
                                              {
                                                  1, 2.3
                                              },
                                    myDict = new
                                             {
                                                 myInt = 1
                                             }
                                };
        
                var myIntOrZero = myobj.GetAsOrDefault< int >( ( Func< int > )( ( ) => myobj.noExist ) );
                int? myNullableInt = GetAs< int >( myobj, ( Func< int > )( ( ) => myobj.myInt ) );
        
                if( default( int ) != myIntOrZero )
                    Console.WriteLine( $"myInt: '{myIntOrZero}'" );
        
                if( default( int? ) != myNullableInt )
                    Console.WriteLine( $"myInt: '{myNullableInt}'" );
        
                if( DoesPropertyExist( myobj, "myInt" ) )
                    Console.WriteLine( $"myInt exists and it is: '{( int )myobj.myInt}'" );
            }
        
            public static bool DoesPropertyExist( dynamic dyn, string property )
            {
                var t = ( Type )dyn.GetType( );
                var props = t.GetProperties( );
                return props.Any( p => p.Name.Equals( property ) );
            }
        
            public static object GetAs< T >( dynamic obj, Func< T > lookup )
            {
                try
                {
                    var val = lookup( );
                    return ( T )val;
                }
                catch( RuntimeBinderException ) { }
        
                return null;
            }
        
            public static T GetAsOrDefault< T >( this object obj, Func< T > test )
            {
                try
                {
                    var val = test( );
                    return ( T )val;
                }
                catch( RuntimeBinderException ) { }
        
                return default( T );
            }
        }
        

        【讨论】:

          【解决方案14】:

          由于ExpandoObject 继承了IDictionary&lt;string, object&gt;,您可以使用以下检查

          dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
          
          if (((IDictionary<string, object>)myVariable).ContainsKey("MyProperty"))    
          //Do stuff
          

          您可以创建一个实用方法来执行此检查,这将使代码更加简洁和可重用

          【讨论】:

          猜你喜欢
          • 2011-09-27
          • 2013-10-31
          • 1970-01-01
          • 1970-01-01
          • 2011-08-12
          • 2017-09-30
          • 1970-01-01
          相关资源
          最近更新 更多