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?
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
}
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.
Peut-être utiliser la réflexion?
dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any();
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);
}
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.
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;
}
}
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;
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;
}
}
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);
}
Je sais que c’est vraiment un vieil article, mais voici une solution simple pour travailler avec dynamic
et saisir c#
.
- peut utiliser une simple réflexion pour énumérer les propriétés directes
- ou peut utiliser la méthode
object
extention- 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 );
}
}
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 ...
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.