Comment détecter si une propriété existe sur un ExpandoObject?

190

En javascript, vous pouvez détecter si une propriété est définie en utilisant le mot-clé undefined:

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

Comment feriez-vous cela en C # en utilisant le mot-clé dynamic avec ExpandoObjectet sans lever d'exception?

Softlion
la source
5
@CodeInChaos: Notez que le code affiché ne vérifie pas la valeur de data.myProperty; il vérifie ce qui typeof data.myPropertyrevient. Il est correct que cela data.myPropertypuisse exister et être défini sur undefined, mais dans ce cas, typeofil renverra autre chose que "undefined". Donc, ce code fonctionne.
Aasmund Eldhuset

Réponses:

181

Selon MSDN, la déclaration montre qu'elle implémente IDictionary:

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

Vous pouvez l'utiliser pour voir si un membre est défini:

var expandoObject = ...;
if(((IDictionary<String, object>)expandoObject).ContainsKey("SomeMember")) {
    // expandoObject.SomeMember exists.
}
Dykam
la source
3
Pour rendre cette vérification plus simple, j'ai surchargé TryGetValue et je l'ai toujours retourné true, en définissant la valeur de retour sur "undefined" si la propriété n'était pas définie. if (someObject.someParam! = "undefined") ... Et ça marche :)
Softlion
Également possible :), mais je pense que vous vouliez dire «surchargé» au lieu de surchargé.
Dykam
oui. J'ai de nouveau changé "undefined" en valeur const d'un objet spécial que j'ai créé ailleurs. Cela évite les problèmes de casting: p
Softlion
1
Je pense que cette solution est toujours d'actualité; ne prenez pas la parole de personne pour le prix de la réflexion - testez-le par vous-même et voyez si vous pouvez vous le permettre
nik.shornikov
1
@ BlueRaja-DannyPflughoeft Oui, ça l'est. Sans la distribution, ce ne sera qu'une invocation dynamique, avec le cas que vous obtenez dans les internes. Plus précisément, il est explicitement implémenté: github.com/mono/mono/blob/master/mcs/class/dlr/Runtime/…
Dykam
29

Une distinction importante doit être faite ici.

La plupart des réponses ici sont spécifiques à ExpandoObject qui est mentionné dans la question. Mais une utilisation courante (et une raison de se poser sur cette question lors de la recherche) est l'utilisation du ViewBag ASP.Net MVC. C'est une implémentation / sous-classe personnalisée de DynamicObject, qui ne lèvera pas d'exception lorsque vous vérifiez un nom de propriété arbitraire pour null. Supposons que vous puissiez déclarer une propriété comme:

@{
    ViewBag.EnableThinger = true;
}

Supposons alors que vous vouliez vérifier sa valeur, et si elle est même définie - si elle existe. Ce qui suit est valide, compilera, ne lèvera aucune exception et vous donnera la bonne réponse:

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

Débarrassez-vous maintenant de la déclaration de EnableThinger. Le même code se compile et s'exécute correctement. Pas besoin de réflexion.

Contrairement à ViewBag, ExpandoObject lancera si vous vérifiez la valeur null sur une propriété qui n'existe pas. Afin d'obtenir les fonctionnalités plus douces de MVC ViewBag de vos dynamicobjets, vous devrez utiliser une implémentation de dynamique qui ne lance pas.

Vous pouvez simplement utiliser l'implémentation exacte dans 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

Vous pouvez le voir lié aux vues MVC ici, dans MVC ViewPage:

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

La clé du comportement gracieux de DynamicViewDataDictionary est l'implémentation du dictionnaire sur ViewDataDictionary, ici:

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

In other words, it always returns a value for all keys, regardless of what's in it - it simply returns null when nothing's there. But, ViewDataDictionary has the burden of being tied to MVC's Model, so it's better to strip out just the graceful dictionary parts for use outside MVC Views.

It's too long to really post all the guts here - most of it just implementing IDictionary - but here's a dynamic object (class DDict) that doesn't throw for null checks on properties that haven't been declared, on Github:

https://github.com/b9chris/GracefulDynamicDictionary

If you just want to add it to your project via NuGet, its name is GracefulDynamicDictionary.

Chris Moschini
la source
Why did you vote down on DynamicDictionary as it does not use reflection then ?
Softlion
then you can vote it up as this is the same solution :)
Softlion
3
It most certainly is not the same solution.
Chris Moschini
"you'll need to create a similar subclass that doesn't throw when a property isn't found." => it is ! Oh no it isn't. My solution is better. It throws - because we want it, AND it can also not throw if TryXX is used;
Softlion
2
THIS, THIS is exactly why I am here, I could not figure out why some code (viewbag) was NOT breaking. THANK you.
Adam Tolley
11

I answered a very similar question recently: How do I reflect over the members of dynamic object?

Shortly, ExpandoObject is not the only dynamic object you might get. Reflection would work for static types (types that do not implement IDynamicMetaObjectProvider). For types that do implement this interface, reflection is basically useless. For ExpandoObject, you can simply check whether the property is defined as a key in the underlying dictionary. For other implementations, it might be challenging and sometimes the only way is to work with exceptions. For details, follow the link above.

Alexandra Rusina
la source
11

UPDATED: You can use delegates and try to get a value from the dynamic object property if it exists. If there is no property, simply catch the exception and return false.

Take a look, it works fine for me:

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

The output of the code is the following:

True
True
False
Alexander G
la source
2
@marklam it's bad to catch all the exception when you don't know what causes the exception. In our cases it's ok since we expect possibly absence of a field.
Alexander G
3
if you know what causes the exception you must also know its type, so catch (WhateverException) Otherwise your code will silently carry on even if you got an unexpected exception - like OutOfMemoryException for example.
marklam
4
You can pass in any getter to IsPropertyExist. In this example, you know one can throw an InvalidOperationException. In practice, you have no idea what exception may be thrown. +1 to counteract the cargo cult.
piedar
2
This solution is unacceptable if performance is important, for example if used in a loop with 500+ iterations it adds up and can cause many seconds of delay. Every time an exception is caught the stack must be copied to the exception object
Jim109
1
Re: Performance: The debugger being attached and Console.WriteLine are the slow bits. 10,000 iterations here takes less than 200ms (with 2 exceptions per iteration). The same test with no exceptions takes a handful of milliseconds. That means if you expect your usage of this code to only rarely be missing a property, or if you're calling it a limited number of times, or can cache the results, then please realize that everything has its place and none of the overly-regurgitated warnings here matter.
Chaos
11

I wanted to create an extension method so I could do something like:

dynamic myDynamicObject;
myDynamicObject.propertyName = "value";

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

... but you can't create extensions on ExpandoObject according to the C# 5 documentation (more info here).

So I ended up creating a class helper:

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

To use it:

// If the 'MyProperty' property exists...
if (ExpandoObjectHelper.HasProperty(obj, "MyProperty"))
{
    ...
}
Maxime
la source
4
up vote for useful comment and link on extensions for ExpandoObject.
Roberto
obj could be null, hence: return obj != null && ((IDictionary<string, object>)obj).ContainsKey(propertyName);
Matěj Štágl
1

Why you do not want to use Reflection to get set of type properyes? Like this

 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
 } 
Vokinneberg
la source
I'm not sure if that will return the dynamicly added properties, my guess is that it returns the methods of the Dynamic object.
Dykam
1

This extension method checks for the existence of a property and then returns the value or null. This is useful if you do not want your applications to throw unnecessary exceptions, at least ones you can help.

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

If can be used as such :

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

or if your local variable is 'dynamic' you will have to cast it to ExpandoObject first.

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

Depending on your use case, if null can be considered as being the same as undefined, you can turn your ExpandoObject into a 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"));

Output:

a is 1
b is 2.5
c is undefined
Wolfgang Grinfeld
la source
-2
(authorDynamic as ExpandoObject).Any(pair => pair.Key == "YourProp");
Kasper Roma
la source
-3

Hey guys stop using Reflection for everything it costs a lots of CPU cycles.

Here is the solution:

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;
    }
}
Softlion
la source
4
This shows how to implement a dynamic object, not how to see it a property exits on a dynamic object.
Matt Warren
You can check if a dynamic instance has a property by doing a null check against the property in question.
ctorx
2
"This shows how to implement a dynamic object": yes in fact it is. The solution to this question is: there is no generic solution as it depends on the implementation.
Softlion
@Softlion No, the solution is that thing that we have to stop using
nik.shornikov
@Softlion What is the point of the Tryxxx methods? TryGet will never return false when it doesn't find the property, so you still have to check the result. The return is useless. In TrySet, if the key does not exist, then it will throw an exception instead of returning false. I don't understand why you would even use this as an answer, if you yourself wrote here on the comments "The solution to this question is: there is no generic solution as it depends on the implementation", that's also not true. Look at Dykam's answer for the real solution.
pqsk
-5

Try this one

public bool PropertyExist(object obj, string propertyName)
{
 return obj.GetType().GetProperty(propertyName) != null;
}
Venkat
la source
3
This would check the existence of a property of the object hidden under the dynamic name, which is an implementation detail. Have you verified your solution in real code before posting ? It should not work at all.
Softlion
I have used this piece of code in real time scenario. It works good.
Venkat
6
Gives me null all the time, even if the property exists.
atlantis
It would work with basic objects, but not with ExpandoObjects. Dynamics, not sure.
Joe
1
To confirm, this does not work with dynamic objects either (always returns null).
Gone Coding