Sprawdź, czy właściwość jest dostępna dla zmiennej dynamicznej

225

Moja sytuacja jest bardzo prosta. Gdzieś w moim kodzie mam to:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Zasadniczo moje pytanie brzmi: jak sprawdzić (bez zgłaszania wyjątku), czy pewna właściwość jest dostępna w mojej zmiennej dynamicznej. Mógłbym to zrobić, GetType()ale wolałbym tego uniknąć, ponieważ tak naprawdę nie muszę znać typu obiektu. Wszystko, co naprawdę chcę wiedzieć, to czy właściwość (lub metoda, jeśli to ułatwia życie) jest dostępna. Jakieś wskazówki?

zaokrąglenie
źródło
1
Jest tu kilka sugestii: stackoverflow.com/questions/2985161/… - ale jak dotąd nie przyjęto żadnej odpowiedzi.
Andrew Anderson
dzięki, widzę, jak zrobić jodłę jednym z rozwiązań, zastanawiałem się, czy czegoś mi brakuje
roundcrisis

Odpowiedzi:

159

Myślę, że nie ma sposobu, aby dowiedzieć się, czy dynamiczmienna ma określony element członkowski bez próby uzyskania do niego dostępu, chyba że ponownie zaimplementowano sposób obsługi powiązania dynamicznego w kompilatorze C #. Który prawdopodobnie zawiera wiele zgadywania, ponieważ jest on zdefiniowany w implementacji, zgodnie ze specyfikacją C #.

Więc powinieneś spróbować uzyskać dostęp do członka i złapać wyjątek, jeśli się nie powiedzie:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 
svick
źródło
2
Oznaczę to jako odpowiedź, ponieważ była tak długa, że ​​wydaje się najlepszą odpowiedzią
roundcrisis
8
Lepsze rozwiązanie - stackoverflow.com/questions/2839598/…
ministrymason
20
@ adminymason Jeśli masz na myśli rzutowanie IDictionaryi pracę z tym, działa to tylko na ExpandoObject, nie będzie działać na żadnym innym dynamicobiekcie.
sick
5
RuntimeBinderExceptionjest w Microsoft.CSharp.RuntimeBinderprzestrzeni nazw.
DavidRR
8
Nadal mam ochotę używać try / catch zamiast, jeśli / else jest ogólnie złą praktyką, bez względu na specyfikę tego scenariusza.
Alexander Ryan Baggett
74

Myślałem, że zrobię porównanie odpowiedzi Martijna i Svicka ...

Następujący program zwraca następujące wyniki:

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

W rezultacie sugeruję użycie odbicia. Patrz poniżej.


W odpowiedzi na komentarz Bland:

Wskaźniki to reflection:exceptiontiki dla 100 000 iteracji:

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

... dość uczciwie - jeśli spodziewasz się, że zawiedzie z prawdopodobieństwem mniejszym niż ~ 1/47, to idź na wyjątek.


Powyższe zakłada, że ​​biegasz za GetProperties()każdym razem. Możesz przyspieszyć ten proces, buforując wyniki GetProperties()dla każdego typu w słowniku lub podobnym. Może to pomóc, jeśli ciągle sprawdzasz ten sam zestaw typów.

dav_i
źródło
7
Zgadzam się i lubię refleksję w mojej pracy, w stosownych przypadkach. Korzyści, jakie ma on w stosunku do Try / Catch, występują wyłącznie po zgłoszeniu wyjątku. Więc o co ktoś powinien zapytać przed skorzystaniem z refleksji - czy to może być jakiś sposób? Czy Twój kod minie 90%, a nawet 75% czasu? Wtedy Try / Catch jest nadal optymalny. Jeśli jest w powietrzu, lub zbyt wiele opcji, aby jedna była najbardziej prawdopodobna, twoje odbicie jest na miejscu.
nijakie
@bland Edytowana odpowiedź.
dav_i
1
Dzięki, wygląda teraz naprawdę kompletnie.
nijakie
@dav_i porównanie obu nie jest sprawiedliwe, ponieważ oba zachowują się inaczej. Odpowiedź Svicka jest bardziej kompletna.
nawfal
1
@dav_i Nie, nie wykonują tej samej funkcji. Odpowiedź Martijna sprawdza, czy właściwość istnieje w typowym typie czasu kompilacji w C #, który jest zadeklarowany jako dynamiczny (co oznacza, że ​​ignoruje kontrole bezpieczeństwa czasu kompilacji). Natomiast odpowiedź svicka sprawdza, czy właściwość istnieje na naprawdę dynamicznym obiekcie, tj. Czymś , co się implementuje IIDynamicMetaObjectProvider. Rozumiem motywację twojej odpowiedzi i doceniam ją. Odpowiedź jest sprawiedliwa.
nawfal
52

Może użyć refleksji?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
Martijn
źródło
2
Cytat z pytania „. Mógłbym zrobić GetType (), ale wolałbym tego uniknąć”
roundcrisis
Czy to nie ma tych samych wad, co moja sugestia? RouteValueDictionary używa odbicia, aby uzyskać właściwości .
Steve Wilkes,
12
Możesz po prostu Where.Any(p => p.Name.Equals("PropertyName"))
zrezygnować
Proszę zobaczyć moją odpowiedź dla porównania odpowiedzi.
dav_i
3
W jednej wkładki: ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). Rzutowanie na typ jest wymagane, aby kompilator był zadowolony z lambda.
MushinNoShin,
38

Na wszelki wypadek pomaga komuś:

Jeśli metoda GetDataThatLooksVerySimilarButNotTheSame()zwróci ExpandoObject, możesz także rzutować na IDictionaryprzed sprawdzeniem.

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

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
karask
źródło
3
Nie jestem pewien, dlaczego ta odpowiedź nie ma więcej głosów, ponieważ robi dokładnie to, o co prosiliśmy (bez wyjątku rzucenia lub refleksji).
Wolfshead,
7
@Wolfshead Ta odpowiedź jest świetna, jeśli wiesz, że twoim obiektem dynamicznym jest ExpandoObject lub coś innego, co implementuje IDictionary <ciąg, obiekt>, ale jeśli zdarzy się, że jest to coś innego, to się nie powiedzie.
Damian Powell
9

Dwa typowe rozwiązania tego problemu obejmują nawiązywanie połączenia i łapanie go RuntimeBinderException, używanie refleksji do sprawdzania połączenia lub szeregowanie do formatu tekstowego i analizowanie stamtąd. Problem z wyjątkami polega na tym, że są one bardzo wolne, ponieważ gdy jeden jest konstruowany, aktualny stos wywołań jest szeregowany. Serializacja do JSON lub coś analogicznego wiąże się z podobną karą. To pozostawia nam refleksję, ale działa tylko wtedy, gdy obiekt leżący pod spodem jest w rzeczywistości POCO z prawdziwymi elementami. Jeśli jest to dynamiczne opakowanie wokół słownika, obiektu COM lub zewnętrznej usługi internetowej, odbicie nie pomoże.

Innym rozwiązaniem jest użycie DynamicMetaObjectnazw, aby uzyskać nazwy członków, tak jak je widzi DLR. W poniższym przykładzie używam klasy statycznej ( Dynamic) do testowania Agepola i wyświetlenia go.

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
źródło
Okazuje się, że Dynamiteypakiet nuget już to robi. ( nuget.org/packages/Dynamitey )
Damian Powell
8

Odpowiedź Denisa skłoniła mnie do zastanowienia się nad innym rozwiązaniem wykorzystującym JsonObjects,

moduł sprawdzania właściwości nagłówka:

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

a może lepiej:

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

na przykład:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;
Charles HETIER
źródło
1
Czy jest szansa, aby dowiedzieć się, co jest nie tak z tą odpowiedzią?
Charles HETIER
Nie wiem, dlaczego to zostało odrzucone, działało świetnie dla mnie. Przeniosłem predykat dla każdej właściwości do klasy pomocniczej i wywołałem metodę Invoke, aby zwrócić wartość logiczną z każdej z nich.
markp3rry
7

Cóż, napotkałem podobny problem, ale w testach jednostkowych.

Za pomocą SharpTestsEx możesz sprawdzić, czy istnieje właściwość. Używam tego do testowania moich kontrolerów, ponieważ ponieważ obiekt JSON jest dynamiczny, ktoś może zmienić nazwę i zapomnieć o zmianie w javascript lub coś takiego, więc testowanie wszystkich właściwości podczas pisania kontrolera powinno zwiększyć moje bezpieczeństwo.

Przykład:

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

Teraz, używając SharTestsEx:

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

Korzystając z tego, testuję wszystkie istniejące właściwości za pomocą „Should (). NotThrow ()”.

Prawdopodobnie nie jest to temat, ale może być przydatne dla kogoś.

Diego Santin
źródło
Dzięki, bardzo przydatna. Korzystając z SharpTestsEx, używam następującego wiersza do przetestowania wartości właściwości dynamicznej:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Remko Jansen,
2

Zgodnie z odpowiedzią @karask, możesz zawinąć funkcję jako pomocnik w ten sposób:

public static bool HasProperty(ExpandoObject expandoObj,
                               string name)
{
    return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}
Wolfshead
źródło
2

Dla mnie to działa:

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;
    }
}
Błazen
źródło
nullnie oznacza, że ​​właściwość nie istnieje
quetzalcoatl
Wiem, ale jeśli ma wartość zero, nie muszę nic robić z tą wartością, więc dla mojego przypadku użycia jest w porządku
Jester
0

Jeśli kontrolujesz typ używany jako dynamiczny, czy nie możesz zwrócić krotki zamiast wartości dla każdego dostępu do właściwości? Coś jak...

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

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

Być może jest to naiwna implementacja, ale jeśli za każdym razem zbudujesz jedną z nich wewnętrznie i zwrócisz ją zamiast rzeczywistej wartości, możesz sprawdzić Existsdostęp do każdej właściwości, a następnie nacisnąć, Valuejeśli ma wartość bycia default(T)(i nieistotną), jeśli tak nie jest.

To powiedziawszy, być może brakuje mi pewnej wiedzy o tym, jak działa dynamika, i może to nie być praktyczną sugestią.

Shibumi
źródło
0

W moim przypadku musiałem sprawdzić, czy istnieje metoda o określonej nazwie, więc użyłem do tego interfejsu

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

Interfejsy mogą zawierać nie tylko metody:

Interfejsy mogą zawierać metody, właściwości, zdarzenia, indeksatory lub dowolną kombinację tych czterech typów elementów.

Z: Interfaces (C # Programming Guide)

Elegancki i nie musisz wychwytywać wyjątków ani bawić się refleksją ...

Fred Mauroy
źródło
0

Wiem, że to bardzo stary post, ale tutaj jest to proste rozwiązanie do pracy z dynamictypu w c#.

  1. potrafi zastosować proste odbicie do wyliczenia bezpośrednich właściwości
  2. lub może użyć objectmetody rozszerzenia
  3. lub użyj GetAsOrDefault<int>metody, aby uzyskać nowy obiekt o silnym typie z wartością, jeśli istnieje, lub wartością domyślną, jeśli nie istnieje.
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
źródło
0

Jak ExpandoObjectdziedziczy IDictionary<string, object>, możesz użyć następującego sprawdzenia

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Możesz wykonać metodę narzędziową do wykonania tej kontroli, która sprawi, że kod będzie znacznie czystszy i będzie można go ponownie użyć

Mukesh Bhojwani
źródło
-1

Oto inny sposób:

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
źródło
2
skąd pomysł, że chodzi o testowanie właściwości JObject? Twoja odpowiedź jest ograniczona do obiektów / klas, które eksponują IEnumerable na ich właściwości. Nie gwarantuje tego dynamic. dynamicsłowo kluczowe jest znacznie szerszym tematem. Sprawdź, czy możesz sprawdzić Countw dynamic foo = new List<int>{ 1,2,3,4 }ten sposób
quetzalcoatl