web-dev-qa-db-fra.com

Teste si une propriété est disponible sur une variable dynamique

Ma situation est très simple. Quelque part dans mon code, j'ai ceci:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

//How to do this?
if (myVariable.MyProperty.Exists)   
//Do stuff

En gros, ma question est donc de savoir comment vérifier (sans lever d'exception) qu'une certaine propriété est disponible sur ma variable dynamique. Je pourrais faire GetType() mais je préférerais l'éviter car je n'ai pas vraiment besoin de connaître le type d'objet. Tout ce que je veux vraiment savoir, c'est si une propriété (ou une méthode, si cela facilite la vie) est disponible. Des pointeurs?

195
roundcrisis

Je pense qu'il est impossible de savoir si une variable dynamic a un membre donné sans essayer d'y accéder, à moins que vous n'ayez réimplémenté la manière dont la liaison dynamique est gérée dans le compilateur C #. Ce qui inclurait probablement beaucoup de devinettes, car il est défini par l'implémentation, conformément à la spécification C #.

Donc, vous devriez réellement essayer d'accéder au membre et attraper une exception, si elle échoue:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Je pensais comparer les réponses de Martijn et de svick ...

Le programme suivant renvoie les résultats suivants:

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

En conséquence, je suggérerais d'utiliser la réflexion. Voir ci-dessous.


En réponse au commentaire de fade:

Les ratios sont des ticks reflection:exception pour 100 000 itérations:

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

... assez bien - si vous vous attendez à ce qu'il échoue avec une probabilité inférieure à ~ 1/47, optez pour exception.


Ce qui précède suppose que vous exécutez GetProperties() à chaque fois. Vous pourrez peut-être accélérer le processus en mettant en cache le résultat de GetProperties() pour chaque type dans un dictionnaire ou similaire. Cela peut aider si vous vérifiez plusieurs fois le même ensemble de types.

64
dav_i

Peut-être utiliser la réflexion?

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

Juste au cas où cela aiderait quelqu'un:

Si la méthode GetDataThatLooksVerySimilarButNotTheSame() renvoie ExpandoObject, vous pouvez également transtyper en IDictionary avant de vérifier. 

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

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

Eh bien, j'ai rencontré un problème similaire mais sur des tests unitaires.

En utilisant SharpTestsEx, vous pouvez vérifier si une propriété existe. J'utilise cette méthode pour tester mes contrôleurs, car l'objet JSON étant dynamique, quelqu'un peut modifier le nom et oublier de le changer dans le javascript. Le test de toutes les propriétés lors de l'écriture du contrôleur devrait augmenter ma sécurité.

Exemple:

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

Maintenant, en utilisant SharTestsEx:

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

En utilisant cela, je teste toutes les propriétés existantes en utilisant "Should (). NotThrow ()".

C'est probablement hors sujet, mais peut être utile pour quelqu'un.

7
Diego Santin

Les deux solutions courantes à cette opération sont de passer un appel et de saisir la RuntimeBinderException, d’utiliser la réflexion pour vérifier l’appel ou de procéder à une sérialisation dans un format de texte et à l’analyse à partir de là. Le problème avec les exceptions est qu’elles sont très lentes, car lorsqu’une est construite, la pile d’appels actuelle est sérialisée. Sérialiser sur JSON ou quelque chose d'analogue entraîne une pénalité similaire. Cela nous laisse avec réflexion mais cela ne fonctionne que si l'objet sous-jacent est en réalité un POCO avec de vrais membres dessus. S'il s'agit d'un wrapper dynamique autour d'un dictionnaire, d'un objet COM ou d'un service Web externe, la réflexion ne vous aidera pas.

Une autre solution consiste à utiliser la variable DynamicMetaObject pour obtenir les noms des membres tels que les voit le DLR. Dans l'exemple ci-dessous, j'utilise une classe statique (Dynamic) pour tester le champ Age et l'afficher.

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

La réponse de Denis m'a fait penser à une autre solution utilisant JsonObjects,

un vérificateur de propriétés d'en-tête:

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

ou peut-être mieux:

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

par exemple:

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

Pour moi cela fonctionne:

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

Suite à la réponse de @karask, vous pouvez envelopper la fonction comme aide, comme ceci:

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

Je sais que c’est vraiment un vieil article, mais voici une solution simple pour travailler avec dynamic et saisir c#.

  1. peut utiliser une simple réflexion pour énumérer les propriétés directes
  2. ou peut utiliser la méthode object extention
  3. ou utilisez la méthode GetAsOrDefault<int> pour obtenir un nouvel objet fortement typé avec une valeur s'il existe ou une valeur par défaut s'il n'en existe pas.
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 );
    }
}
0
SimperT

Dans mon cas, je devais vérifier l’existence d’une méthode portant un nom spécifique, j’ai donc utilisé une interface pour cela.

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

En outre, les interfaces peuvent contenir plus que de simples méthodes:

Les interfaces peuvent contenir des méthodes, des propriétés, des événements, des indexeurs ou n'importe quel combinaison de ces quatre types de membres.

De: Interfaces (Guide de programmation C #)

Élégant et inutile de piéger les exceptions ou de jouer avec la réflexion ...

0
Fred Mauroy

Si vous contrôlez le type utilisé en tant que dynamique, ne pouvez-vous pas renvoyer un tuple au lieu d'une valeur pour chaque accès à une propriété? Quelque chose comme...

public class DynamicValue<T>
{
    internal DynamicValue(T value, bool exists)
    {
         Value = value;
         Exists = exists;
    }

    T Value { get; private set; }
    bool Exists { get; private set; }
}

Peut-être une implémentation naïve, mais si vous construisez un de ceux-ci en interne à chaque fois et que vous renvoyez cela au lieu de la valeur réelle, vous pouvez vérifier Exists sur chaque accès à la propriété, puis cliquer sur Value si la valeur est default(T) (et non pertinente) si elle ne le fait pas. 't.

Cela dit, il se peut que je manque de connaissances sur la dynamique et que cela ne soit pas une suggestion viable.

0
Shibumi