我的情况很简单。在我的代码中,我有这样的代码:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

因此,基本上我的问题是如何检查(不抛出异常)某个属性在我的动态变量上是可用的。我可以使用GetType(),但我宁愿避免这样做,因为我并不真正需要知道对象的类型。我真正想知道的是是否有一个属性(或方法,如果这使工作更简单的话)可用。指针吗?


当前回答

我想我应该比较一下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"));
}

因此,我建议使用反射。见下文。


回应bland的评论:

比率是反射:100000次迭代的异常滴答声:

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()结果缓存到字典或类似内容中来加快该过程。如果您要反复检查同一组类型,这可能会有所帮助。

其他回答

对我来说,这是可行的:

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

我想我应该比较一下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"));
}

因此,我建议使用反射。见下文。


回应bland的评论:

比率是反射:100000次迭代的异常滴答声:

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()结果缓存到字典或类似内容中来加快该过程。如果您要反复检查同一组类型,这可能会有所帮助。

由于ExpandoObject继承了IDictionary<string对象>,您可以使用以下检查

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

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

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;

根据@karask的回答,你可以像这样将函数包装为一个helper:

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