Jak wykryć, czy właściwość istnieje na ExpandoObject?

188

W javascript możesz wykryć, czy właściwość jest zdefiniowana za pomocą niezdefiniowanego słowa kluczowego:

if( typeof data.myProperty == "undefined" ) ...

Jak zrobiłbyś to w C # używając dynamicznego słowa kluczowego ExpandoObjectzi bez zgłaszania wyjątku?

Softlion
źródło
5
@CodeInChaos: Zauważ, że wyświetlany kod nie sprawdza wartości data.myProperty; sprawdza, co typeof data.myPropertyzwraca. To prawda, że data.myPropertymoże istnieć i być ustawione undefined, ale w takim przypadku typeofzwróci coś innego niż "undefined". Więc ten kod działa.
Aasmund Eldhuset

Odpowiedzi:

181

Według MSDN deklaracja pokazuje, że implementuje IDictionary:

public sealed class ExpandoObject : IDynamicMetaObjectProvider, 
    IDictionary<string, Object>, ICollection<KeyValuePair<string, Object>>, 
    IEnumerable<KeyValuePair<string, Object>>, IEnumerable, INotifyPropertyChanged

Możesz użyć tego, aby sprawdzić, czy członek jest zdefiniowany:

var expandoObject = ...;
if(((IDictionary<String, object>)expandoObject).ContainsKey("SomeMember")) {
    // expandoObject.SomeMember exists.
}
Dykam
źródło
3
Aby uprościć tę kontrolę, przeciążyłem TryGetValue i sprawiam, że zawsze zwraca wartość true, ustawiając wartość zwracaną na „niezdefiniowana”, jeśli właściwość nie została zdefiniowana. if (someObject.someParam! = "undefined") ... I to działa :)
Softlion
Możliwe również :), ale myślę, że miałeś na myśli „zastąpiony” zamiast przeciążony.
Dykam
tak. Znów zmieniłem „niezdefiniowany” na stałą obiektu specjalnego, którą utworzyłem gdzie indziej. Zapobiega problemom z rzucaniem: p
Softlion
1
Wierzę, że to rozwiązanie jest wciąż aktualne; nie
wierz
1
@ BlueRaja-DannyPflughoeft Tak, jest. Bez obsady będzie to tylko dynamiczne wywołanie, z przypadkiem, w którym dostaniesz wnętrze. Mówiąc dokładniej, jest to wyraźnie zaimplementowane: github.com/mono/mono/blob/master/mcs/class/dlr/Runtime/…
Dykam
28

Należy tu dokonać istotnego rozróżnienia.

Większość odpowiedzi tutaj jest specyficznych dla ExpandoObject, który jest wymieniony w pytaniu. Ale powszechnym zastosowaniem (i powodem, dla którego warto znaleźć to pytanie podczas wyszukiwania) jest użycie ASP.Net MVC ViewBag. Jest to niestandardowa implementacja / podklasa DynamicObject, która nie zgłosi wyjątku, gdy sprawdzisz dowolną nazwę właściwości pod kątem wartości null. Załóżmy, że możesz zadeklarować właściwość taką jak:

@{
    ViewBag.EnableThinger = true;
}

Załóżmy, że chcesz sprawdzić jego wartość i czy w ogóle jest ustawiona - czy istnieje. Poniższe informacje są prawidłowe, będą się kompilować, nie będą zgłaszać żadnych wyjątków i dadzą właściwą odpowiedź:

if (ViewBag.EnableThinger != null && ViewBag.EnableThinger)
{
    // Do some stuff when EnableThinger is true
}

Teraz pozbądź się deklaracji EnableThinger. Ten sam kod kompiluje się i działa poprawnie. Nie ma potrzeby refleksji.

W przeciwieństwie do ViewBag, ExpandoObject wyrzuci, jeśli zaznaczysz null dla właściwości, która nie istnieje. Aby uzyskać delikatniejszą funkcjonalność MVC ViewBag z twoich dynamicobiektów, musisz użyć implementacji dynamiki, która nie rzuca.

Możesz po prostu użyć dokładnej implementacji w MVC ViewBag:

. . .
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
    result = ViewData[binder.Name];
    // since ViewDataDictionary always returns a result even if the key does not exist, always return true
    return true;
}
. . .

https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/DynamicViewDataDictionary.cs

Możesz zobaczyć, że jest on powiązany z widokami MVC tutaj, w MVC ViewPage:

http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/ViewPage.cs

Kluczem do wdzięcznego zachowania DynamicViewDataDictionary jest implementacja Dictionary na ViewDataDictionary, tutaj:

public object this[string key]
{
    get
    {
        object value;
        _innerDictionary.TryGetValue(key, out value);
        return value;
    }
    set { _innerDictionary[key] = value; }
}

https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/ViewDataDictionary.cs

Innymi słowy, zawsze zwraca wartość dla wszystkich kluczy, niezależnie od tego, co jest w środku - po prostu zwraca wartość null, gdy nic tam nie ma. Ale ViewDataDictionary ma ciężar związany z modelem MVC, więc lepiej jest usunąć tylko pełne wdzięku części słownika do użycia poza widokami MVC.

Naprawdę za długo, żeby opublikować tutaj wszystkie wnętrzności - większość z nich to tylko implementacja IDictionary - ale tutaj jest dynamiczny obiekt (klasa DDict), który nie wyrzuca zerowych kontroli właściwości, które nie zostały zadeklarowane, na Github:

https://github.com/b9chris/GracefulDynamicDictionary

Jeśli chcesz tylko dodać go do swojego projektu za pomocą NuGet, jego nazwa to GracefulDynamicDictionary .

Chris Moschini
źródło
Dlaczego głosowałeś na DynamicDictionary, ponieważ nie używa on wtedy refleksji?
Softlion
wtedy możesz zagłosować, ponieważ jest to to samo rozwiązanie :)
Softlion
3
Z pewnością nie jest to to samo rozwiązanie.
Chris Moschini,
„musisz utworzyć podobną podklasę, która nie będzie generowana, gdy właściwość nie zostanie znaleziona”. => to jest! O nie, nie jest. Moje rozwiązanie jest lepsze. Rzuca - bo tego chcemy, I nie może też rzucać, jeśli użyjemy TryXX;
Softlion
1
TO właśnie dlatego właśnie tu jestem, nie mogłem zrozumieć, dlaczego jakiś kod (viewbag) NIE łamał się. Dziękuję Ci.
Adam Tolley,
11

Odpowiedziałem ostatnio na bardzo podobne pytanie: jak mogę zastanowić się nad elementami dynamicznego obiektu?

Krótko mówiąc, ExpandoObject nie jest jedynym dynamicznym obiektem, który możesz uzyskać. Odbicie działałoby dla typów statycznych (typy, które nie implementują IDynamicMetaObjectProvider). W przypadku typów, które implementują ten interfejs, odbicie jest w zasadzie bezużyteczne. W przypadku ExpandoObject możesz po prostu sprawdzić, czy właściwość jest zdefiniowana jako klucz w podstawowym słowniku. W przypadku innych implementacji może to być trudne, a czasem jedynym sposobem jest praca z wyjątkami. Aby uzyskać szczegółowe informacje, kliknij powyższy link.

Alexandra Rusina
źródło
11

ZAKTUALIZOWANO: Możesz użyć delegatów i spróbować uzyskać wartość z właściwości obiektu dynamicznego, jeśli istnieje. Jeśli nie ma właściwości, po prostu złap wyjątek i zwróć false.

Spójrz, działa dla mnie dobrze:

class Program
{
    static void Main(string[] args)
    {
        dynamic userDynamic = new JsonUser();

        Console.WriteLine(IsPropertyExist(() => userDynamic.first_name));
        Console.WriteLine(IsPropertyExist(() => userDynamic.address));
        Console.WriteLine(IsPropertyExist(() => userDynamic.last_name));
    }

    class JsonUser
    {
        public string first_name { get; set; }
        public string address
        {
            get
            {
                throw new InvalidOperationException("Cannot read property value");
            }
        }
    }

    static bool IsPropertyExist(GetValueDelegate getValueMethod)
    {
        try
        {
            //we're not interesting in the return value. What we need to know is whether an exception occurred or not
            getValueMethod();
            return true;
        }
        catch (RuntimeBinderException)
        {
            // RuntimeBinderException occurred during accessing the property
            // and it means there is no such property         
            return false;
        }
        catch
        {
            //property exists, but an exception occurred during getting of a value
            return true;
        }
    }

    delegate string GetValueDelegate();
}

Kod wyjściowy jest następujący:

True
True
False
Alexander G.
źródło
2
@marklam źle jest złapać cały wyjątek, gdy nie wiesz, co powoduje wyjątek. W naszych przypadkach jest w porządku, ponieważ spodziewamy się braku pola.
Alexander G
3
jeśli wiesz, co powoduje wyjątek, musisz również znać jego typ, więc wyłapuj (WhthingException) W przeciwnym razie kod będzie dyskretnie kontynuował, nawet jeśli otrzymasz nieoczekiwany wyjątek - na przykład OutOfMemoryException.
marklam
4
Możesz przekazać dowolny getter do IsPropertyExist. W tym przykładzie wiesz, że można rzucić InvalidOperationException. W praktyce nie masz pojęcia, jaki wyjątek może zostać zgłoszony. +1, aby przeciwdziałać kultowi ładunku.
piedar
2
To rozwiązanie jest niedopuszczalne, jeśli wydajność jest ważna, na przykład w przypadku pętli z ponad 500 iteracjami sumuje się i może powodować wiele sekund opóźnienia. Za każdym razem, gdy wychwytywany jest wyjątek, stos musi zostać skopiowany do obiektu wyjątku
Jim109
1
Re: Wydajność: Dołączany jest debugger i Console.WriteLine są wolnymi bitami. 10 000 iteracji tutaj zajmuje mniej niż 200 ms (z 2 wyjątkami na iterację). Ten sam test bez wyjątków zajmuje kilka milisekund. Oznacza to, że jeśli spodziewasz się, że użycie tego kodu rzadko brakuje właściwości lub jeśli nazywasz ją ograniczoną liczbę razy lub możesz buforować wyniki, to pamiętaj, że wszystko ma swoje miejsce i żadne z nadmiernie -regurgitowane ostrzeżenia tutaj mają znaczenie.
Chaos
10

Chciałem stworzyć metodę rozszerzenia, aby móc zrobić coś takiego:

dynamic myDynamicObject;
myDynamicObject.propertyName = "value";

if (myDynamicObject.HasProperty("propertyName"))
{
    //...
}

... ale nie możesz tworzyć rozszerzeń ExpandoObjectzgodnie z dokumentacją w C # 5 (więcej informacji tutaj ).

Więc ostatecznie stworzyłem pomocnika klasy:

public static class ExpandoObjectHelper
{
    public static bool HasProperty(ExpandoObject obj, string propertyName)
    {
        return ((IDictionary<String, object>)obj).ContainsKey(propertyName);
    }
}

Aby go użyć:

// If the 'MyProperty' property exists...
if (ExpandoObjectHelper.HasProperty(obj, "MyProperty"))
{
    ...
}
Maxime
źródło
4
głosuj na użyteczny komentarz i link do rozszerzeń dla ExpandoObject.
Roberto
1

Dlaczego nie chcesz używać Reflection, aby uzyskać zestaw propery typu? Lubię to

 dynamic v = new Foo();
 Type t = v.GetType();
 System.Reflection.PropertyInfo[] pInfo =  t.GetProperties();
 if (Array.Find<System.Reflection.PropertyInfo>(pInfo, p => { return p.Name == "PropName"; }).    GetValue(v,  null) != null))
 {
     //PropName initialized
 } 
Vokinneberg
źródło
Nie jestem pewien, czy to zwróci dynamicznie dodane właściwości, domyślam się, że zwróci metody obiektu dynamicznego.
Dykam
1

Ta metoda rozszerzenia sprawdza istnienie właściwości, a następnie zwraca wartość lub wartość null. Jest to przydatne, jeśli nie chcesz, aby aplikacje zgłaszały niepotrzebne wyjątki, przynajmniej takie, w których możesz pomóc.

    public static object Value(this ExpandoObject expando, string name)
    {
        var expandoDic = (IDictionary<string, object>)expando;
        return expandoDic.ContainsKey(name) ? expandoDic[name] : null;
    }

Jeśli może być używany jako taki:

  // lookup is type 'ExpandoObject'
  object value = lookup.Value("MyProperty");

lub jeśli twoja zmienna lokalna jest „dynamiczna”, musisz najpierw rzucić ją na ExpandoObject.

  // lookup is type 'dynamic'
  object value = ((ExpandoObject)lookup).Value("PropertyBeingTested");
realdanielbyrne
źródło
1

W zależności od przypadku użycia, jeśli wartość null może być uznana za taką samą jak niezdefiniowana, możesz zmienić ExpandoObject w DynamicJsonObject.

    dynamic x = new System.Web.Helpers.DynamicJsonObject(new ExpandoObject());
    x.a = 1;
    x.b = 2.50;
    Console.WriteLine("a is " + (x.a ?? "undefined"));
    Console.WriteLine("b is " + (x.b ?? "undefined"));
    Console.WriteLine("c is " + (x.c ?? "undefined"));

Wynik:

a is 1
b is 2.5
c is undefined
Wolfgang Grinfeld
źródło
-2
(authorDynamic as ExpandoObject).Any(pair => pair.Key == "YourProp");
Kasper Roma
źródło
-3

Hej, przestańcie używać Reflection do wszystkiego, co kosztuje dużo cykli procesora.

Oto rozwiązanie:

public class DynamicDictionary : DynamicObject
{
    Dictionary<string, object> dictionary = new Dictionary<string, object>();

    public int Count
    {
        get
        {
            return dictionary.Count;
        }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        string name = binder.Name;

        if (!dictionary.TryGetValue(binder.Name, out result))
            result = "undefined";

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        dictionary[binder.Name] = value;
        return true;
    }
}
Softlion
źródło
4
To pokazuje, jak zaimplementować obiekt dynamiczny, a nie jak zobaczyć, jak właściwość wychodzi na obiekt dynamiczny.
Matt Warren
Możesz sprawdzić, czy instancja dynamiczna ma właściwość, wykonując kontrolę zerową względem tej właściwości.
ctorx
2
„To pokazuje, jak zaimplementować obiekt dynamiczny”: tak w rzeczywistości jest. Rozwiązaniem tego pytania jest: nie ma ogólnego rozwiązania, ponieważ zależy to od implementacji.
Softlion
@Softlion No, rozwiązaniem jest to, że rzeczy, które możemy mieć do zaprzestania używania
nik.shornikov
@Softlion Jaki jest sens metod Tryxxx? TryGet nigdy nie zwróci false, jeśli nie znajdzie właściwości, więc nadal musisz sprawdzić wynik. Zwrot jest bezużyteczny. W TrySet, jeśli klucz nie istnieje, zwróci wyjątek zamiast zwracać wartość false. Nie rozumiem, dlaczego użyłbyś tego nawet jako odpowiedzi, jeśli sam napisałeś tutaj w komentarzach „Rozwiązanie tego pytania brzmi: nie ma ogólnego rozwiązania, ponieważ zależy to od implementacji”, to również nie jest prawdą. Spójrz na odpowiedź Dykama na prawdziwe rozwiązanie.
pqsk
-5

Spróbuj tego

public bool PropertyExist(object obj, string propertyName)
{
 return obj.GetType().GetProperty(propertyName) != null;
}
Venkat
źródło
3
Sprawdziłoby to istnienie właściwości obiektu ukrytego pod nazwą dynamiczną, która jest szczegółem implementacji. Czy sprawdziłeś swoje rozwiązanie w prawdziwym kodzie przed opublikowaniem? To nie powinno w ogóle działać.
Softlion
Użyłem tego fragmentu kodu w scenariuszu w czasie rzeczywistym. Działa dobrze.
Venkat
6
Daje mi cały czas wartość zerową, nawet jeśli właściwość istnieje.
Atlantis
Działa z podstawowymi obiektami, ale nie z ExpandoObjects. Dynamika, nie jestem pewien.
Joe
1
Aby potwierdzić, nie działa to również z dynamicobiektami (zawsze zwraca null).
Gone Coding