Testen Sie, ob eine Eigenschaft für eine dynamische Variable verfügbar ist

225

Meine Situation ist sehr einfach. Irgendwo in meinem Code habe ich Folgendes:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Meine Frage ist also im Grunde, wie ich überprüfen kann (ohne eine Ausnahme auszulösen), ob eine bestimmte Eigenschaft für meine dynamische Variable verfügbar ist. Ich könnte es tun, GetType()aber ich würde das lieber vermeiden, da ich den Typ des Objekts nicht wirklich kennen muss. Alles, was ich wirklich wissen möchte, ist, ob eine Eigenschaft (oder eine Methode, wenn das das Leben leichter macht) verfügbar ist. Irgendwelche Hinweise?

Rundkrise
quelle
1
Hier gibt es einige Vorschläge: stackoverflow.com/questions/2985161/… - aber bisher keine akzeptierte Antwort.
Andrew Anderson
danke, ich kann sehen, wie man eine der Lösungen zu einer Lösung macht, obwohl ich mich gefragt habe, ob ich etwas verpasse
Roundcrisis

Antworten:

159

Ich denke, es gibt keine Möglichkeit herauszufinden, ob eine dynamicVariable ein bestimmtes Mitglied hat, ohne zu versuchen, darauf zuzugreifen, es sei denn, Sie haben die Art und Weise, wie die dynamische Bindung im C # -Compiler behandelt wird, erneut implementiert. Dies würde wahrscheinlich viel Vermutung beinhalten, da es gemäß der C # -Spezifikation implementierungsdefiniert ist.

Sie sollten also tatsächlich versuchen, auf das Mitglied zuzugreifen und eine Ausnahme abzufangen, wenn dies fehlschlägt:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 
svick
quelle
2
Ich werde dies als die Antwort markieren, da es so lange her ist, dass es die beste Antwort zu sein scheint
Roundcrisis
8
Bessere Lösung - stackoverflow.com/questions/2839598/…
Ministrymason
20
@ministrymason Wenn Sie das Casting IDictionaryund Arbeiten damit meinen , funktioniert das nur bei ExpandoObject, es funktioniert bei keinem anderen dynamicObjekt.
Svick
5
RuntimeBinderExceptionist im Microsoft.CSharp.RuntimeBinderNamespace.
DavidRR
8
Ich habe immer noch das Gefühl, try / catch zu verwenden, anstatt ob / else im Allgemeinen eine schlechte Praxis ist, unabhängig von diesen Besonderheiten dieses Szenarios.
Alexander Ryan Baggett
74

Ich dachte, ich würde Martijns Antwort und Svicks Antwort vergleichen ...

Das folgende Programm gibt die folgenden Ergebnisse zurück:

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

Daher würde ich vorschlagen, Reflexion zu verwenden. Siehe unten.


Auf Blands Kommentar antworten:

Verhältnisse sind reflection:exceptionTicks für 100000 Iterationen:

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

... fair genug - wenn Sie erwarten, dass es mit einer Wahrscheinlichkeit von weniger als ~ 1/47 fehlschlägt, dann gehen Sie zur Ausnahme.


Das oben Gesagte setzt voraus, dass Sie GetProperties()jedes Mal laufen . Möglicherweise können Sie den Prozess beschleunigen, indem Sie das Ergebnis GetProperties()für jeden Typ in einem Wörterbuch oder ähnlichem zwischenspeichern. Dies kann hilfreich sein, wenn Sie immer wieder mit denselben Typen vergleichen.

dav_i
quelle
7
Ich stimme zu und mag gegebenenfalls Reflexion in meiner Arbeit. Die Gewinne, die es gegenüber Try / Catch hat, sind nur, wenn die Ausnahme ausgelöst wird. Also, was sollte jemand fragen, bevor er hier Reflexion verwendet - ist es wahrscheinlich ein bestimmter Weg? Wird Ihr Code in 90% oder sogar 75% der Fälle weitergegeben? Dann ist Try / Catch immer noch optimal. Wenn es in der Luft liegt oder zu viele Möglichkeiten für eine wahrscheinlichste, dann ist Ihr Spiegelbild genau richtig.
milder
@bland Antwort bearbeitet.
Dav_i
1
Danke, sieht jetzt wirklich komplett aus.
fade
@dav_i es ist nicht fair, beide zu vergleichen, da sich beide unterschiedlich verhalten. Die Antwort von svick ist vollständiger.
Nawfal
1
@dav_i Nein, sie führen nicht die gleiche Funktion aus. Martijns Antwort prüft, ob eine Eigenschaft für einen regulären Kompilierungszeittyp in C # vorhanden ist, der als dynamisch deklariert ist (was bedeutet , dass Sicherheitsüberprüfungen für die Kompilierungszeit ignoriert werden). Während die Antwort von svick prüft, ob eine Eigenschaft für ein wirklich dynamisches Objekt vorhanden ist, dh etwas, das implementiert wird IIDynamicMetaObjectProvider. Ich verstehe die Motivation hinter Ihrer Antwort und schätze sie. Es ist fair, so zu antworten.
Nawfal
52

Vielleicht Reflexion verwenden?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
Martijn
quelle
2
Zitat aus der Frage ". Ich könnte GetType () machen, aber ich würde das lieber vermeiden"
Roundcrisis
Hat dies nicht die gleichen Nachteile wie mein Vorschlag? RouteValueDictionary verwendet Reflektion, um Eigenschaften abzurufen .
Steve Wilkes
12
Sie können einfach verzichten auf Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i
Bitte sehen Sie meine Antwort für den Antwortvergleich.
Dav_i
3
Als Einzeiler : ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). Die Umwandlung in Typ ist erforderlich, um den Compiler über das Lambda glücklich zu machen.
MushinNoShin
37

Nur für den Fall, dass es jemandem hilft:

Wenn die Methode eine GetDataThatLooksVerySimilarButNotTheSame()zurückgibt ExpandoObject, können Sie diese IDictionaryvor der Überprüfung auch in a umwandeln.

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

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
Karask
quelle
3
Ich bin mir nicht sicher, warum diese Antwort nicht mehr Stimmen hat, weil sie genau das tut, wonach gefragt wurde (kein Ausnahmewurf oder Reflexion).
Wolfshead
7
@Wolfshead Diese Antwort ist großartig, wenn Sie wissen, dass Ihr dynamisches Objekt ein ExpandoObject oder etwas anderes ist, das IDictionary <Zeichenfolge, Objekt> implementiert. Wenn es sich jedoch um etwas anderes handelt, schlägt dies fehl.
Damian Powell
9

Die beiden gängigen Lösungen hierfür sind das Tätigen und Abfangen des Anrufs, das RuntimeBinderExceptionVerwenden der Reflexion zum Überprüfen des Anrufs oder das Serialisieren in ein Textformat und das Parsen von dort aus. Das Problem mit Ausnahmen ist, dass sie sehr langsam sind, da bei der Erstellung der aktuelle Aufrufstapel serialisiert wird. Die Serialisierung auf JSON oder etwas Ähnliches führt zu einer ähnlichen Strafe. Dies lässt uns nachdenken, funktioniert aber nur, wenn das zugrunde liegende Objekt tatsächlich ein POCO mit echten Mitgliedern ist. Wenn es sich um einen dynamischen Wrapper um ein Wörterbuch, ein COM-Objekt oder einen externen Webdienst handelt, hilft die Reflexion nicht weiter.

Eine andere Lösung besteht darin DynamicMetaObject, die Mitgliedsnamen so zu ermitteln, wie sie vom DLR angezeigt werden. Im folgenden Beispiel verwende ich eine statische Klasse ( Dynamic), um das AgeFeld zu testen und anzuzeigen.

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
quelle
Es stellt sich heraus, dass das DynamiteyNuget-Paket dies bereits tut. ( nuget.org/packages/Dynamitey )
Damian Powell
8

Die Antwort von Denis ließ mich über eine andere Lösung mit JsonObjects nachdenken.

ein Header-Eigenschaftsprüfer:

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

oder vielleicht besser:

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

beispielsweise:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;
Charles HETIER
quelle
1
Gibt es eine Chance zu wissen, was mit dieser Antwort falsch ist, bitte?
Charles HETIER
Ich weiß nicht, warum dies abgelehnt wurde, es hat großartig für mich funktioniert. Ich habe das Prädikat für jede Eigenschaft in eine Hilfsklasse verschoben und die Invoke-Methode aufgerufen, um von jeder einen Bool zurückzugeben.
Markp3rry
7

Nun, ich hatte ein ähnliches Problem, aber bei Unit-Tests.

Mit SharpTestsEx können Sie überprüfen, ob eine Eigenschaft vorhanden ist. Ich verwende diese Tests, um meine Controller zu testen, da das JSON-Objekt dynamisch ist und jemand den Namen ändern und vergessen kann, ihn im Javascript oder so zu ändern. Daher sollte das Testen aller Eigenschaften beim Schreiben des Controllers meine Sicherheit erhöhen.

Beispiel:

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

Verwenden Sie jetzt SharTestsEx:

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

Damit teste ich alle vorhandenen Eigenschaften mit "Should (). NotThrow ()".

Es ist wahrscheinlich nicht zum Thema, kann aber für jemanden nützlich sein.

Diego Santin
quelle
Danke, sehr nützlich. Mit SharpTestsEx benutze ich die folgende Zeile, um auch den Wert der dynamischen Eigenschaft zu testen:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen
2

In Anlehnung an die Antwort von @karask können Sie die Funktion als Helfer wie folgt umschließen:

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

Bei mir funktioniert das:

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;
    }
}
Narr
quelle
nullbedeutet nicht, dass die Eigenschaft nicht existiert
quetzalcoatl
Ich weiß, aber wenn es null ist, muss ich nichts mit dem Wert tun, daher ist es für meinen Anwendungsfall in Ordnung
Jester
0

Wenn Sie den als dynamisch verwendeten Typ steuern, können Sie dann nicht für jeden Eigenschaftszugriff ein Tupel anstelle eines Werts zurückgeben? Etwas wie...

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

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

Möglicherweise eine naive Implementierung, aber wenn Sie jedes Mal eine davon intern erstellen und diese anstelle des tatsächlichen Werts zurückgeben, können Sie Existsjeden Eigenschaftszugriff überprüfen und dann drücken, Valueob dies der Fall ist default(T)(und irrelevant), wenn dies nicht der Fall ist.

Trotzdem fehlt mir möglicherweise etwas Wissen darüber, wie Dynamik funktioniert, und dies ist möglicherweise kein praktikabler Vorschlag.

Shibumi
quelle
0

In meinem Fall musste ich prüfen, ob eine Methode mit einem bestimmten Namen vorhanden ist, daher habe ich dafür eine Schnittstelle verwendet

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

Außerdem können Schnittstellen mehr als nur Methoden enthalten:

Schnittstellen können Methoden, Eigenschaften, Ereignisse, Indexer oder eine beliebige Kombination dieser vier Elementtypen enthalten.

Von: Schnittstellen (C # -Programmierhandbuch)

Elegant und keine Notwendigkeit, Ausnahmen abzufangen oder mit Reflexion zu spielen ...

Fred Mauroy
quelle
0

Ich weiß , das wirklich alte Post, aber hier ist eine einfache Lösung für die Arbeit mit dynamicArt c#.

  1. kann einfache Reflexion verwenden, um direkte Eigenschaften aufzulisten
  2. oder kann die objectErweiterungsmethode verwenden
  3. Oder verwenden Sie die GetAsOrDefault<int>Methode, um ein neues stark typisiertes Objekt mit Wert abzurufen, falls vorhanden, oder Standard, wenn nicht vorhanden.
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
quelle
0

Als ExpandoObjecterbt das können IDictionary<string, object>Sie die folgende Prüfung verwenden

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Sie können eine Dienstprogrammmethode erstellen, um diese Prüfung durchzuführen, die den Code viel sauberer und wiederverwendbarer macht

Mukesh Bhojwani
quelle
-1

Hier ist der andere Weg:

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
quelle
2
Woher kam die Idee, die Eigenschaften von JObject zu testen? Ihre Antwort ist auf Objekte / Klassen beschränkt, die IEnumerable über ihre Eigenschaften verfügbar machen. Nicht garantiert von dynamic. dynamicSchlüsselwort ist viel weiter gefasst. Gehen Sie überprüfen , ob Sie testen können , Countin dynamic foo = new List<int>{ 1,2,3,4 }wie diese
quetzalcoatl