¿Cómo detectar si existe una propiedad en un ExpandoObject?

188

En javascript puede detectar si una propiedad está definida usando la palabra clave indefinida:

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

¿Cómo haría esto en C # usando la palabra clave dinámica con ExpandoObjecty sin lanzar una excepción?

Softlion
fuente
55
@CodeInChaos: Tenga en cuenta que el código que se muestra no verifica el valor de data.myProperty; Comprueba lo que typeof data.myPropertyvuelve. Es correcto que data.myPropertypueda existir y establecerse en undefined, pero en ese caso, typeofdevolverá algo distinto de "undefined". Entonces este código funciona.
Aasmund Eldhuset

Respuestas:

181

Según MSDN, la declaración muestra que está implementando IDictionary:

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

Puede usar esto para ver si un miembro está definido:

var expandoObject = ...;
if(((IDictionary<String, object>)expandoObject).ContainsKey("SomeMember")) {
    // expandoObject.SomeMember exists.
}
Dykam
fuente
3
Para simplificar esta comprobación, he sobrecargado TryGetValue y hago que siempre devuelva verdadero, estableciendo el valor de retorno en "indefinido" si la propiedad no se definió. if (someObject.someParam! = "undefined") ... Y funciona :)
Softlion
También es posible :), pero creo que quiso decir "anulado" en lugar de sobrecargado.
Dykam
Sí. Nuevamente he cambiado "indefinido" al valor constante de un objeto especial que creé en otro lugar. Previene problemas de lanzamiento: p
Softlion
1
Creo que esta solución aún está vigente; no tome la palabra de nadie por el precio de la reflexión
pruébelo
1
@ BlueRaja-DannyPflughoeft Sí, lo es. Sin el elenco, solo será una invocación dinámica, con el caso que se obtiene en las partes internas. Más específicamente, se implementa de forma explícita: github.com/mono/mono/blob/master/mcs/class/dlr/Runtime/...
dykam
28

Una distinción importante debe hacerse aquí.

La mayoría de las respuestas aquí son específicas del ExpandoObject que se menciona en la pregunta. Pero un uso común (y una razón para aterrizar en esta pregunta cuando se busca) es cuando se usa ASP.Net MVC ViewBag. Esa es una implementación / subclase personalizada de DynamicObject, que no arrojará una excepción cuando verifique cualquier nombre de propiedad arbitrario para nulo. Suponga que puede declarar una propiedad como:

@{
    ViewBag.EnableThinger = true;
}

Luego, suponga que desea verificar su valor y si está configurado, si existe. Lo siguiente es válido, compilará, no arrojará ninguna excepción y le dará la respuesta correcta:

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

Ahora deshazte de la declaración de EnableThinger. El mismo código se compila y se ejecuta correctamente. No hay necesidad de reflexión.

A diferencia de ViewBag, ExpandoObject se lanzará si verifica nulo en una propiedad que no existe. Para sacar la funcionalidad más suave de MVC ViewBag de sus dynamicobjetos, necesitará usar una implementación de dinámica que no arroje.

Simplemente puede usar la implementación exacta en 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

Puede ver que está vinculado a MVC Views aquí, en MVC ViewPage:

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

La clave del comportamiento elegante de DynamicViewDataDictionary es la implementación del Diccionario en ViewDataDictionary, aquí:

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

En otras palabras, siempre devuelve un valor para todas las claves, independientemente de lo que contenga, simplemente devuelve nulo cuando no hay nada allí. Pero, ViewDataDictionary tiene la carga de estar atado al modelo de MVC, por lo que es mejor eliminar solo las elegantes partes del diccionario para usar fuera de las vistas de MVC.

Es demasiado tiempo para publicar realmente todas las agallas aquí, la mayoría solo implementando IDictionary, pero aquí hay un objeto dinámico (clase DDict) que no arroja controles nulos en propiedades que no se han declarado, en Github:

https://github.com/b9chris/GracefulDynamicDictionary

Si solo desea agregarlo a su proyecto a través de NuGet, su nombre es GracefulDynamicDictionary .

Chris Moschini
fuente
¿Por qué votó en contra de DynamicDictionary ya que no usa reflexión entonces?
Softlion
entonces puedes votar, ya que esta es la misma solución :)
Softlion
3
Ciertamente no es la misma solución.
Chris Moschini
"necesitará crear una subclase similar que no se produce cuando no se encuentra una propiedad". => lo es! Oh no, no lo es. Mi solución es mejor Lanza, porque lo queremos, Y tampoco puede lanzar si se usa TryXX;
Softlion
1
ESTO, ESTO es exactamente por qué estoy aquí, no pude entender por qué algún código (viewbag) NO se estaba rompiendo. Gracias.
Adam Tolley
11

Respondí una pregunta muy similar recientemente: ¿Cómo reflexiono sobre los miembros del objeto dinámico?

En breve, ExpandoObject no es el único objeto dinámico que puede obtener. Reflection funcionaría para tipos estáticos (tipos que no implementan IDynamicMetaObjectProvider). Para los tipos que implementan esta interfaz, la reflexión es básicamente inútil. Para ExpandoObject, simplemente puede verificar si la propiedad está definida como una clave en el diccionario subyacente. Para otras implementaciones, puede ser un desafío y, a veces, la única forma es trabajar con excepciones. Para más detalles, siga el enlace de arriba.

Alexandra Rusina
fuente
11

ACTUALIZADO: puede usar delegados e intentar obtener un valor de la propiedad del objeto dinámico si existe. Si no hay propiedad, simplemente captura la excepción y devuelve falso.

Echa un vistazo, me funciona bien:

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

La salida del código es la siguiente:

True
True
False
Alexander G
fuente
2
@marklam es malo detectar todas las excepciones cuando no sabes qué causa la excepción. En nuestros casos está bien, ya que esperamos posiblemente la ausencia de un campo.
Alexander G
3
si sabe qué causa la excepción, también debe conocer su tipo, por lo tanto, capture (WhateverException) De lo contrario, su código continuará silenciosamente incluso si tiene una excepción inesperada, como OutOfMemoryException, por ejemplo.
marklam
44
Puede pasar a cualquier getter a IsPropertyExist. En este ejemplo, sabes que uno puede lanzar un InvalidOperationException. En la práctica, no tienes idea de qué excepción se puede lanzar. +1 para contrarrestar el culto a la carga.
piedar
2
Esta solución es inaceptable si el rendimiento es importante, por ejemplo, si se usa en un bucle con más de 500 iteraciones, suma y puede causar muchos segundos de retraso. Cada vez que se
detecta
1
Re: Rendimiento: El depurador que se adjunta y Console.WriteLine son los bits lentos. 10,000 iteraciones aquí toma menos de 200ms (con 2 excepciones por iteración). La misma prueba sin excepciones lleva unos pocos milisegundos. Eso significa que si espera que el uso de este código rara vez le falte una propiedad, o si lo está llamando un número limitado de veces, o puede almacenar en caché los resultados, entonces tenga en cuenta que todo tiene su lugar y ninguno de los excesivamente advertencias regurgitadas aquí importan.
Caos
10

Quería crear un método de extensión para poder hacer algo como:

dynamic myDynamicObject;
myDynamicObject.propertyName = "value";

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

... pero no puede crear extensiones de ExpandoObjectacuerdo con la documentación de C # 5 (más información aquí ).

Así que terminé creando un ayudante de clase:

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

Para usarlo:

// If the 'MyProperty' property exists...
if (ExpandoObjectHelper.HasProperty(obj, "MyProperty"))
{
    ...
}
Maxime
fuente
44
vote por comentarios útiles y enlace en extensiones para ExpandoObject.
Roberto
1

¿Por qué no desea utilizar Reflection para obtener un conjunto de tipos de propiedad sí? Me gusta esto

 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
fuente
No estoy seguro de si eso devolverá las propiedades agregadas dinámicamente, supongo que devuelve los métodos del objeto dinámico.
Dykam
1

Este método de extensión verifica la existencia de una propiedad y luego devuelve el valor o nulo. Esto es útil si no desea que sus aplicaciones arrojen excepciones innecesarias, al menos las que puede ayudar.

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

Si se puede usar como tal:

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

o si su variable local es 'dinámica', primero deberá convertirla a ExpandoObject.

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

Dependiendo de su caso de uso, si puede considerarse que nulo es igual a indefinido, puede convertir su ExpandoObject en un 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"));

Salida:

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

Hola chicos, dejen de usar Reflection para todo lo que cuesta muchos ciclos de CPU.

Aquí está la solución:

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
fuente
44
Esto muestra cómo implementar un objeto dinámico, no cómo verlo, una propiedad existe en un objeto dinámico.
Matt Warren
Puede verificar si una instancia dinámica tiene una propiedad haciendo una verificación nula de la propiedad en cuestión.
ctorx
2
"Esto muestra cómo implementar un objeto dinámico": sí, de hecho lo es. La solución a esta pregunta es: no existe una solución genérica, ya que depende de la implementación.
Softlion
@Softlion No, la solución es que lo que tenemos que dejar de usar
nik.shornikov
@Softlion ¿Cuál es el objetivo de los métodos Tryxxx? TryGet nunca devolverá falso cuando no encuentre la propiedad, por lo que aún debe verificar el resultado. El regreso es inútil. En TrySet, si la clave no existe, arrojará una excepción en lugar de devolver false. No entiendo por qué incluso usaría esto como respuesta, si usted mismo escribió aquí en los comentarios "La solución a esta pregunta es: no hay una solución genérica, ya que depende de la implementación", eso tampoco es cierto. Mire la respuesta de Dykam para la solución real.
pqsk
-5

Prueba este

public bool PropertyExist(object obj, string propertyName)
{
 return obj.GetType().GetProperty(propertyName) != null;
}
Venkat
fuente
3
Esto comprobaría la existencia de una propiedad del objeto oculto bajo el nombre dinámico, que es un detalle de implementación. ¿Ha verificado su solución en código real antes de publicar? No debería funcionar en absoluto.
Softlion
He usado este fragmento de código en un escenario en tiempo real. Funciona bien
Venkat
66
Me da nulo todo el tiempo, incluso si la propiedad existe.
atlantis
Funcionaría con objetos básicos, pero no con ExpandoObjects. Dinámica, no estoy seguro.
Joe
1
Para confirmar, esto tampoco funciona con dynamicobjetos (siempre regresa null).
Ido codificación