Проверьте, доступно ли свойство для динамической переменной - PullRequest
205 голосов
/ 08 июня 2010

Моя ситуация очень проста.Где-то в моем коде у меня есть это:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Итак, в основном мой вопрос состоит в том, как проверить (без исключения), что определенное свойство доступно для моей динамической переменной.Я мог бы сделать GetType(), но я бы предпочел этого избежать, поскольку мне не нужно знать тип объекта.Все, что я действительно хочу знать, это доступность свойства (или метода, если это облегчает жизнь).Есть указатели?

Ответы [ 13 ]

149 голосов
/ 24 апреля 2011

Я думаю, что нет никакого способа выяснить, есть ли у переменной dynamic определенный член, не пытаясь получить к нему доступ, если только вы не реализовали способ динамического связывания, обрабатываемый в компиляторе C #. Что, вероятно, будет включать много догадок, потому что это определяется реализацией в соответствии со спецификацией C #.

Таким образом, вы должны попытаться получить доступ к члену и поймать исключение, если оно завершится неудачей:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 
66 голосов
/ 15 ноября 2013

Я думал, что сделаю сравнение ответа Мартина и ответа svick ...

Следующая программа возвращает следующие результаты:

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

В результате я бы предложил использовать отражение. См. Ниже.


Ответ наКомментарий Бланда:

Соотношения reflection:exception тиков для 100000 итераций:

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

... достаточно справедливо - если вы ожидаете, что он потерпит неудачу с вероятностью менее ~ 1/47, затем перейдите к исключению.


Выше предполагается, что вы запускаете GetProperties() каждый раз.Вы можете ускорить процесс, кэшируя результат GetProperties() для каждого типа в словаре или аналогичном.Это может помочь, если вы снова и снова проверяете один и тот же набор типов.

44 голосов
/ 03 мая 2011

Может быть, использовать отражение?

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
31 голосов
/ 17 апреля 2015

На всякий случай, если кому-то это поможет:

Если метод GetDataThatLooksVerySimilarButNotTheSame() возвращает ExpandoObject, вы также можете привести к IDictionary перед проверкой.

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

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
8 голосов
/ 12 июня 2015

Ответ Дениса заставил меня задуматься над другим решением, использующим JsonObjects,

проверка свойства заголовка:

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

или, может быть, лучше:

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

например:

dynamic json = JsonConvert.DeserializeObject(data);
string header = hasHeader(json) ? json.header : null;
7 голосов
/ 28 июля 2016

Два распространенных решения этой проблемы включают в себя выполнение вызова и перехват RuntimeBinderException, использование отражения для проверки вызова или сериализацию в текстовый формат и анализ оттуда. Проблема с исключениями заключается в том, что они очень медленные, потому что при их создании текущий стек вызовов сериализуется. Сериализация в JSON или что-то аналогичное влечет за собой аналогичное наказание. Это оставляет нам отражение, но работает только в том случае, если базовый объект на самом деле является POCO с реальными членами. Если это динамическая оболочка вокруг словаря, COM-объекта или внешнего веб-сервиса, то рефлексия не поможет.

Другим решением является использование DynamicMetaObject для получения имен членов в том виде, в каком их видит DLR. В приведенном ниже примере я использую статический класс (Dynamic) для проверки поля Age и его отображения.

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 голосов
/ 13 января 2012

Ну, я столкнулся с подобной проблемой, но на модульных тестах.

Используя SharpTestsEx, вы можете проверить, существует ли свойство.Я использую это тестирование своих контроллеров, потому что, поскольку объект JSON является динамическим, кто-то может изменить имя и забыть изменить его в javascript или что-то в этом роде, поэтому тестирование всех свойств при написании контроллера должно повысить мою безопасность.

Пример:

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

Теперь, используя SharTestsEx:

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

Используя это, я проверяю все существующие свойства, используя "Should (). NotThrow ()".

Это, вероятно, не в тему, но может быть полезно для кого-то.

2 голосов
/ 21 июня 2016

Исходя из ответа @karask, вы можете обернуть функцию как помощник так:

public static bool HasProperty(ExpandoObject expandoObj,
                               string name)
{
    return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}
2 голосов
/ 06 декабря 2014

Для меня это работает:

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;
    }
}
0 голосов
/ 28 марта 2019

Я знаю, что это действительно старый пост, но вот простое решение для работы с dynamic, набрав c#.

  1. может использовать простое отражение для перечисления прямых свойств
  2. или может использовать метод расширения object
  3. или использовать метод GetAsOrDefault<int> для полученияновый строго типизированный объект со значением, если существует, или по умолчанию, если не существует.
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 );
    }
}
...