Pregunta

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 hay una determinada propiedad disponible en mi variable dinámica. Yo podría hacer 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 hace la vida más fácil) está disponible. ¿Algún consejo?

¿Fue útil?

Solución

Creo que no hay forma de averiguar si un dynamic La variable tiene un cierto miembro sin intentar acceder a él, a menos que vuelva a implementar la forma en que se maneja la unión dinámica en el compilador C#. Lo que probablemente incluiría muchas adivinanzas, porque está definida por la implementación, según la especificación C#.

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Otros consejos

Pensé que haría una comparación de Respuesta de Martijn y 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.


Respondiendo al comentario de Bland:

Las proporciones son reflection:exception garrapatas 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

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


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

¿Quizás usar la reflexión?

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

Por si acaso ayuda a alguien:

Si el método GetDataThatLooksVerySimilarButNotTheSame() Devuelve un ExpandoObject También puedes lanzar a un IDictionary antes de comprobar.

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

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}

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

un verificador de propiedad del 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;

Las dos soluciones comunes a esto incluyen hacer la llamada y captar el RuntimeBinderException, usando la reflexión para verificar la llamada, o serializar a un formato de texto y análisis desde allí. El problema con las excepciones es que son muy lentos, porque cuando se construye uno, la pila de llamadas actual se está serializada. Serialización a JSON o algo análogo incurre en una penalización similar. Esto nos deja con la reflexión, pero solo funciona si el objeto subyacente es en realidad un POCO con miembros reales. Si se trata de un envoltorio dinámico 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 el DLR. En el ejemplo a continuación, uso una clase estática (Dynamic) para probar el Age campo 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;
    }
}

Bueno, enfrenté un problema similar pero en las 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();

Usando esto, pruebo todas las propiedades existentes usando "debería (). Notthrow ()".

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

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

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

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

Si controla el tipo que se usa como dinámica, ¿no podría devolver una tupla en lugar de un valor para cada acceso a la 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 Exists En cada acceso a la propiedad y luego presione Value Si lo hace con el valor ser default(T) (e irrelevante) Si no es así.

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

En mi caso, necesitaba verificar la existencia de un método con un nombre específico, por lo que utilicé 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 reflexión ...

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

  1. puede usar una reflexión simple para enumerar las propiedades directas
  2. o puede usar el object método de extensión
  3. o usar GetAsOrDefault<int> Método para obtener un nuevo objeto fuertemente escrito 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 );
    }
}

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;
            }
        }
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top