Pruebe si una propiedad está disponible en una variable dinámica

Mi situación es muy simple. En algún lugar de mi código tengo esto:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame(); //How to do this? if (myVariable.MyProperty.Exists) //Do stuff 

Entonces, básicamente mi pregunta es cómo verificar (sin lanzar una excepción) que cierta propiedad está disponible en mi variable dinámica. Podría hacer GetType() pero preferiría evitarlo ya que realmente no necesito saber el tipo de objeto. Todo lo que realmente quiero saber es si una propiedad (o método, si eso hace la vida más fácil) está disponible. ¿Alguna sugerencia?

Creo que no hay forma de averiguar si una variable dynamic tiene un miembro determinado sin intentar acceder a ella, a menos que se vuelva a implementar la forma en que se maneja el enlace dynamic en el comstackdor de C #. Lo que probablemente incluiría muchas suposiciones, porque está definido por la implementación, de acuerdo con la especificación C #.

Por lo tanto, debería intentar acceder al miembro y detectar una excepción, si falla:

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

Pensé que haría una comparación de la respuesta de Martijn y la respuesta de svick …

El siguiente progtwig arroja los siguientes resultados:

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

Como resultado, sugiero usar el reflection. Vea abajo.


Respondiendo al comentario de bland:

Las razones son reflection:exception marcas de reflection:exception para 100000 iteraciones:

 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 

… lo suficientemente justo: si espera que falle con una probabilidad de menos de ~ 1/47, entonces vaya a la excepción.


Lo anterior supone que está ejecutando GetProperties() cada vez. Puede acelerar el proceso guardando en caché el resultado de GetProperties() para cada tipo en un diccionario o similar. Esto puede ayudar si revisa una y otra vez el mismo conjunto de tipos.

Tal vez usar el reflection?

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

En caso de que ayude a alguien:

Si el método GetDataThatLooksVerySimilarButNotTheSame() devuelve un ExpandoObject , también puede convertirlo a un IDictionary antes de verificar.

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

Bueno, me enfrenté a un problema similar, pero en pruebas unitarias.

Usando SharpTestsEx puedes verificar si existe una propiedad. Utilizo esta prueba de mis controladores, porque como el objeto JSON es dynamic, alguien puede cambiar el nombre y olvidarse de cambiarlo en javascript o algo así, por lo que probar todas las propiedades al escribir el controlador debería boost mi seguridad.

Ejemplo:

 dynamic testedObject = new ExpandoObject(); testedObject.MyName = "I am a testing object"; 

Ahora, usando SharTestsEx:

 Executing.This(delegate {var unused = testedObject.MyName; }).Should().NotThrow(); Executing.This(delegate {var unused = testedObject.NotExistingProperty; }).Should().Throw(); 

Usando esto, pruebo todas las propiedades existentes usando “Should (). NotThrow ()”.

Probablemente esté fuera de tema, pero puede ser útil para alguien.

Las dos soluciones comunes para esto incluyen realizar la llamada y capturar la RuntimeBinderException , usar la reflexión para verificar la llamada o serializar a un formato de texto y analizar a partir de allí. El problema con las excepciones es que son muy lentos, porque cuando uno se construye, la stack de llamadas actual se serializa. Serializar a JSON o algo análogo incurre en una penalización similar. Esto nos deja reflexionados, pero solo funciona si el objeto subyacente es en realidad un POCO con miembros reales en él. Si se trata de un contenedor dynamic alrededor de un diccionario, un objeto COM o un servicio web externo, entonces la reflexión no ayudará.

Otra solución es usar el DynamicMetaObject para obtener los nombres de los miembros como los ve DLR. En el siguiente ejemplo, utilizo una clase estática ( Dynamic ) para probar el campo Age y mostrarlo.

 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 GetMemberNames(object dynObj) { var metaObjProvider = dynObj as IDynamicMetaObjectProvider; if (null == metaObjProvider) throw new InvalidOperationException( "The supplied object must be a dynamic object " + "(ie it must implement IDynamicMetaObjectProvider)" ); var metaObj = metaObjProvider.GetMetaObject( Expression.Constant(metaObjProvider) ); var memberNames = metaObj.GetDynamicMemberNames(); return memberNames; } } 

La respuesta de Denis me hizo pensar en otra solución usando JsonObjects,

un inspector de propiedades del encabezado:

 Predicate hasHeader = jsonObject => ((JObject)jsonObject).OfType() .Any(prop => prop.Name == "header"); 

o tal vez mejor:

 Predicate hasHeader = jsonObject => ((JObject)jsonObject).Property("header") != null; 

por ejemplo:

 dynamic json = JsonConvert.DeserializeObject(data); string header = hasHeader(json) ? json.header : null; 

Para mí esto funciona:

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

Siguiendo con la respuesta de @karask, podrías envolver la función como un ayudante así:

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

Si controla el tipo que se usa como dynamic, ¿no podría devolver una tupla en lugar de un valor por cada acceso a la propiedad? Algo como…

 public class DynamicValue { internal DynamicValue(T value, bool exists) { Value = value; Exists = exists; } T Value { get; private set; } bool Exists { get; private set; } } 

Posiblemente una implementación ingenua, pero si construye internamente cada una de ellas y devuelve eso en lugar del valor real, puede verificar Exists en cada acceso de propiedad y luego presionar Value si lo hace con valor default(T) (e irrelevante) si no es así

Dicho esto, podría faltar algo de conocimiento sobre cómo funciona el dynamic y esta podría no ser una sugerencia viable.

En mi caso, necesitaba verificar la existencia de un método con un nombre específico, así que usé una interfaz para ese

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

Además, las interfaces pueden contener más que solo métodos:

Las interfaces pueden contener métodos, propiedades, eventos, indizadores o cualquier combinación de esos cuatro tipos de miembros.

De: Interfaces (Guía de progtwigción C #)

Elegante y sin necesidad de atrapar excepciones o jugar con la reflexión …

Aquí está la otra forma:

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