测试动态变量上的属性是否可用

rou*_*sis 217 c# dynamic dynamic-keyword

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

//How to do this?
if (myVariable.MyProperty.Exists)   
//Do stuff
Run Code Online (Sandbox Code Playgroud)

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

svi*_*ick 154

我认为dynamic除非你重新实现在C#编译器中处理动态绑定的方式,否则无法确定变量是否具有某个成员而不尝试访问它.根据C#规范,这可能包括很多猜测,因为它是实现定义的.

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

  • @ministrymason如果你的意思是转换为`IDictionary`并使用它,它只适用于`ExpandoObject`,它将不适用于任何其他`dynamic`对象. (20认同)
  • 更好的解决方案 - http://stackoverflow.com/questions/2839598/how-to-detect-if-a-property-exists-on-a-dynamic-object-in-c (8认同)
  • 我仍然觉得使用try/catch而不是if/else通常是不好的做法,无论这个场景的具体细节如何. (6认同)
  • [`RuntimeBinderException`](https://msdn.microsoft.com/en-us/library/microsoft.csharp.runtimebinder.runtimebinderexception%28v=vs.110%29.aspx)位于`Microsoft.CSharp.RuntimeBinder中命名空间. (5认同)
  • 我会把它标记为答案因为它已经很久了,它似乎确实是最好的答案 (2认同)
  • 实际上我只是尝试了 `var t = dynVar.nonExistentProp;` 并且 t 为空。没有抛出错误。 (2认同)

dav*_*v_i 68

我以为我会比较Martijn的答案svick的回答 ......

以下程序返回以下结果:

Testing with exception: 2430985 ticks
Testing with reflection: 155570 ticks
Run Code Online (Sandbox Code Playgroud)
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"));
}
Run Code Online (Sandbox Code Playgroud)

结果我建议使用反射.见下文.


回应布兰德的评论:

比率是reflection:exception100000次迭代的刻度:

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
Run Code Online (Sandbox Code Playgroud)

......足够公平 - 如果你预计它会以小于〜/ 47的概率失败,那就去寻求例外.


以上假设您GetProperties()每次都在运行.您可以通过缓存GetProperties()字典或类似内容中每种类型的结果来加速该过程.如果您反复检查同一组类型,这可能会有所帮助.

  • 我同意并喜欢在适当的时候反思我的工作.它对Try/Catch的收益仅在抛出异常时.那么在这里使用反射之前应该问什么 - 它可能是某种方式吗?90%甚至75%的时间,你的代码会通过吗?然后Try/Catch仍然是最佳的.如果它在空中,或者最有可能的选择太多,那么你的反思就是现实. (7认同)
  • @dav_i 不,它们不执行相同的功能。Martijn 的回答检查 C# 中的 *regular compile time type* 是否存在属性,即 *declared dynamic*(意味着它忽略编译时安全检查)。而 svick 的回答检查属性是否存在于*真正动态*的对象上,即实现了`IIDynamicMetaObjectProvider`的东西。我确实理解您的回答背后的动机,并对此表示赞赏。这么回答是公平的。 (2认同)

小智 48

也许用反射?

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

  • 你可以不用`Where`:`.Any(p => p.Name.Equals("PropertyName"))` (11认同)
  • 作为一个单行:`((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName"))`.要使编译器对lambda感到满意,需要使用强制转换类型. (3认同)
  • 引用问题".我可以做GetType(),但我宁愿避免这样做" (2认同)

小智 33

以防万一它可以帮助某人:

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

dynamic test = new System.Dynamic.ExpandoObject();
test.foo = "bar";

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
Run Code Online (Sandbox Code Playgroud)

  • @Wolfshead如果你知道你的动态对象是一个ExpandoObject或其他实现IDictionary <string,object>的东西,但是如果碰巧是其他东西,那么这个答案很棒.那么这将失败. (6认同)
  • 不确定为什么这个答案没有更多的选票,因为它完全符合要求(没有例外抛出或反射). (2认同)

Dam*_*ell 9

对此的两个常见解决方案包括进行调用和捕获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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Cha*_*IER 8

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

标题属性检查器:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).OfType<JProperty>()
                                     .Any(prop => prop.Name == "header");
Run Code Online (Sandbox Code Playgroud)

或者更好:

Predicate<object> hasHeader = jsonObject =>
                                 ((JObject)jsonObject).Property("header") != null;
Run Code Online (Sandbox Code Playgroud)

例如:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;
Run Code Online (Sandbox Code Playgroud)


小智 7

好吧,我遇到了类似的问题,但在单元测试中.

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

例:

dynamic testedObject = new ExpandoObject();
testedObject.MyName = "I am a testing object";
Run Code Online (Sandbox Code Playgroud)

现在,使用SharTestsEx:

Executing.This(delegate {var unused = testedObject.MyName; }).Should().NotThrow();
Executing.This(delegate {var unused = testedObject.NotExistingProperty; }).Should().Throw();
Run Code Online (Sandbox Code Playgroud)

使用它,我使用"Should().NotThrow()"测试所有现有属性.

它可能不在话题范围内,但可能对某人有用.