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

225

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 una determinada propiedad esté disponible en mi variable dinámica. Podría hacerlo, GetType()pero prefiero evitar eso, 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 facilita la vida) está disponible. Cualquier puntero?

crisis redonda
fuente
1
Aquí hay un par de sugerencias: stackoverflow.com/questions/2985161/… , pero hasta ahora no se ha aceptado ninguna respuesta.
Andrew Anderson
gracias, puedo ver cómo hacer un abeto de las soluciones, aunque me preguntaba si hay algo que yo estoy perdiendo
roundcrisis

Respuestas:

159

Creo que no hay forma de averiguar si una dynamicvariable tiene un miembro determinado sin intentar acceder a ella, a menos que vuelva a implementar la forma en que se maneja el enlace dinámico en el compilador de C #. Lo que probablemente incluiría muchas conjeturas, 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
} 
svick
fuente
2
Marcaré esto como la respuesta, ya que ha pasado tanto tiempo, parece ser la mejor respuesta
crisis redonda
8
Mejor solución - stackoverflow.com/questions/2839598/…
ministrymason
20
@ministrymason Si te refieres a transmitir IDictionaryy trabajar con eso, eso solo funciona ExpandoObject, no funcionará en ningún otro dynamicobjeto.
svick
55
RuntimeBinderExceptionestá en el Microsoft.CSharp.RuntimeBinderespacio de nombres.
DavidRR
8
Todavía tengo ganas de usar try / catch en lugar de if / else es una mala práctica en general, independientemente de estos detalles de este escenario.
Alexander Ryan Baggett el
74

Yo pensé en hacer una comparación de la respuesta de Martijn y la respuesta de svick ...

El siguiente programa devuelve 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, sugeriría usar la reflexión. Vea abajo.


En respuesta al comentario de bland:

Las relaciones son reflection:exceptionticks 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

... bastante justo: si espera que falle con una probabilidad con menos de ~ 1/47, entonces elija una excepción.


Lo anterior supone que estás corriendo GetProperties()cada vez. Es posible que pueda acelerar el proceso almacenando en caché el resultado de GetProperties()cada tipo en un diccionario o similar. Esto puede ayudar si está comprobando el mismo conjunto de tipos una y otra vez.

dav_i
fuente
77
Estoy de acuerdo y me gusta reflexionar en mi trabajo cuando sea apropiado. Las ganancias que tiene sobre Try / Catch son únicamente cuando se lanza la excepción. Entonces, ¿qué debería preguntar alguien antes de usar la reflexión aquí? ¿Es probable que sea de cierta manera? 90% o incluso el 75% del tiempo, ¿pasará su código? Entonces Try / Catch sigue siendo óptimo. Si está en el aire, o hay demasiadas opciones para que una sea más probable, entonces tu reflejo es perfecto.
soso
@bland Respuesta editada.
dav_i
1
Gracias, parece realmente completo ahora.
soso
@dav_i no es justo comparar ambos, ya que ambos se comportan de manera diferente. La respuesta de svick es más completa.
nawfal
1
@dav_i No, no realizan la misma función. La respuesta de Martijn comprueba si existe una propiedad en un tipo de tiempo de compilación regular en C #, que se declara dinámica (lo que significa que ignora las comprobaciones de seguridad del tiempo de compilación). Mientras que la respuesta de svick verifica si existe una propiedad en un objeto verdaderamente dinámico , es decir, algo que se implemente IIDynamicMetaObjectProvider. Entiendo la motivación detrás de su respuesta, y lo aprecio. Es justo responder eso.
nawfal
52

Tal vez usar la reflexión?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
Martijn
fuente
2
Cita de la pregunta ". Podría hacer GetType () pero prefiero evitar eso"
roundcrisis
¿No tiene esto los mismos inconvenientes que mi sugerencia? RouteValueDictionary utiliza la reflexión para obtener propiedades .
Steve Wilkes
12
Puede prescindir de Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i
Por favor, vea mi respuesta para la comparación de respuestas.
dav_i
3
Como de una sola línea: ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). Se requiere la conversión para escribir para que el compilador esté contento con la lambda.
MushinNoShin
38

Por si acaso ayuda a alguien:

Si el método GetDataThatLooksVerySimilarButNotTheSame()devuelve un ExpandoObjecttambién puede lanzar a IDictionaryantes de verificar.

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

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
karask
fuente
3
No estoy seguro de por qué esta respuesta no tiene más votos, porque hace exactamente lo que se solicitó (sin excepción, proyección o reflexión).
Wolfshead
77
@Wolfshead Esta respuesta es excelente si sabe que su objeto dinámico es un Objeto de Expando o algo más que implementa IDictionary <string, object> pero si resulta ser otra cosa, entonces fallará.
Damian Powell
9

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

Otra solución es usar el DynamicMetaObjectpara obtener los nombres de los miembros como los ve el DLR. En el ejemplo a continuación, uso una clase estática ( Dynamic) para probar el Agecampo 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<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;
    }
}
Damian Powell
fuente
Resulta que el Dynamiteypaquete nuget ya hace esto. ( nuget.org/packages/Dynamitey )
Damian Powell
8

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

un verificador de propiedad de encabezado:

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

o tal vez mejor:

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

por ejemplo:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;
Charles HETIER
fuente
1
¿Existe la posibilidad de saber qué hay de malo en esta respuesta, por favor?
Charles HETIER
No sé por qué esto fue rechazado, funcionó muy bien para mí. Moví el Predicate para cada propiedad a una clase auxiliar y llamé al método Invoke para devolver un bool de cada uno.
markp3rry
7

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

Usando SharpTestsEx puede verificar si existe una propiedad. Utilizo esta prueba de mis controladores, porque dado que el objeto JSON es dinámico, alguien puede cambiar el nombre y olvidar cambiarlo en JavaScript o algo así, por lo que probar todas las propiedades al escribir el controlador debería aumentar 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();

Con esto, pruebo todas las propiedades existentes con "should (). NotThrow ()".

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

Diego Santin
fuente
Gracias muy útil Usando SharpTestsEx, uso la siguiente línea para probar también el valor de la propiedad dinámica:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen
2

Siguiendo con la respuesta de @karask, podría ajustar la función como ayudante de esta manera:

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

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;
    }
    catch (RuntimeBinderException)
    {
        return false;
    }
    catch
    {
        return true;
    }
}
Bufón
fuente
nullno significa que la propiedad no exista
quetzalcoatl
Lo sé, pero si es nulo, no necesito hacer nada con el valor, por lo tanto, para mi caso, está bien
Jester
0

Si controla el tipo que se usa como dinámico, ¿no podría devolver una tupla en lugar de un valor para cada acceso de propiedad? Algo como...

public class DynamicValue<T>
{
    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 uno de estos internamente cada vez y devuelve eso en lugar del valor real, puede verificar Existscada acceso a la propiedad y luego presionar Valuesi lo hace con valor default(T)(e irrelevante) si no lo hace.

Dicho esto, podría estar perdiendo algunos conocimientos sobre cómo funciona la dinámica y esto podría no ser una sugerencia viable.

Shibumi
fuente
0

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

var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(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, indexadores o cualquier combinación de esos cuatro tipos de miembros.

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

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

Fred Mauroy
fuente
0

Sé que esta es una publicación muy antigua, pero aquí hay una solución simple para trabajar con dynamicescribir c#.

  1. puede usar una reflexión simple para enumerar propiedades directas
  2. o puede usar el objectmétodo de extensión
  3. o use el GetAsOrDefault<int>método para obtener un nuevo objeto fuertemente tipado con valor si existe o predeterminado si no existe.
public static class DynamicHelper
{
    private static void Test( )
    {
        dynamic myobj = new
                        {
                            myInt = 1,
                            myArray = new[ ]
                                      {
                                          1, 2.3
                                      },
                            myDict = new
                                     {
                                         myInt = 1
                                     }
                        };

        var myIntOrZero = myobj.GetAsOrDefault< int >( ( Func< int > )( ( ) => myobj.noExist ) );
        int? myNullableInt = GetAs< int >( myobj, ( Func< int > )( ( ) => myobj.myInt ) );

        if( default( int ) != myIntOrZero )
            Console.WriteLine( $"myInt: '{myIntOrZero}'" );

        if( default( int? ) != myNullableInt )
            Console.WriteLine( $"myInt: '{myNullableInt}'" );

        if( DoesPropertyExist( myobj, "myInt" ) )
            Console.WriteLine( $"myInt exists and it is: '{( int )myobj.myInt}'" );
    }

    public static bool DoesPropertyExist( dynamic dyn, string property )
    {
        var t = ( Type )dyn.GetType( );
        var props = t.GetProperties( );
        return props.Any( p => p.Name.Equals( property ) );
    }

    public static object GetAs< T >( dynamic obj, Func< T > lookup )
    {
        try
        {
            var val = lookup( );
            return ( T )val;
        }
        catch( RuntimeBinderException ) { }

        return null;
    }

    public static T GetAsOrDefault< T >( this object obj, Func< T > test )
    {
        try
        {
            var val = test( );
            return ( T )val;
        }
        catch( RuntimeBinderException ) { }

        return default( T );
    }
}
SimperT
fuente
0

Como ExpandoObjecthereda el IDictionary<string, object>puede usar la siguiente comprobación

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Puede hacer un método de utilidad para realizar esta verificación, que hará que el código sea mucho más limpio y reutilizable

Mukesh Bhojwani
fuente
-1

Aquí está la otra manera:

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;
            }
        }
    }
}
Denis
fuente
2
¿De dónde sacaste la idea de que la pregunta es sobre probar las propiedades de JObject? Su respuesta está limitada a objetos / clases que exponen IEnumerable sobre sus propiedades. No garantizada por dynamic. dynamicLa palabra clave es un tema mucho más amplio. Ir cheque si se puede probar Counten dynamic foo = new List<int>{ 1,2,3,4 }el estilo
Quetzalcoatl