Проверьте, доступно ли свойство для динамической переменной

225

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

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

roundcrisis
источник
1
Здесь есть несколько предложений: stackoverflow.com/questions/2985161/… - но пока не принято ответа.
Эндрю Андерсон
спасибо, я вижу, как сделать пихту одним из решений, хотя мне было интересно, есть ли что-то, что я пропускаю
круглый кризис

Ответы:

159

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

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

try
{
    var x = myVariable.MyProperty;
    // do stuff with x
}
catch (RuntimeBinderException)
{
    //  MyProperty doesn't exist
} 
svick
источник
2
Я отмечу это как ответ, так как это было так долго, это, кажется, лучший ответ
раунд
8
Лучшее решение - stackoverflow.com/questions/2839598/…
ministrymason
20
@ministrymason Если вы имеете в виду приведение IDictionaryи работу с этим, который работает только над ним ExpandoObject, он не будет работать ни с каким другим dynamicобъектом.
свик
5
RuntimeBinderExceptionнаходится в Microsoft.CSharp.RuntimeBinderпространстве имен.
DavidRR
8
Я все еще чувствую, что пытаюсь использовать try / catch вместо if / else в целом, несмотря на специфику этого сценария.
Александр Райан Баггетт
74

Я думал , что я хотел бы сделать сравнение ответа Мартейн в и ответ 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()для каждого типа в словаре или подобном. Это может помочь, если вы снова и снова проверяете один и тот же набор типов.

dav_i
источник
7
Я согласен и люблю отражение в моей работе, где это уместно. Прибыль, которую он имеет над Try / Catch, имеет место только тогда, когда выдается исключение. Так что кто-то должен спросить, прежде чем использовать отражение здесь - это может быть определенный путь? 90% или даже 75% времени, ваш код пройдет? Тогда Try / Catch все еще оптимально. Если он в воздухе, или слишком много вариантов для одного, чтобы быть наиболее вероятным, то ваше отражение на месте.
мягкий
@bland Отредактированный ответ.
Дав_и
1
Спасибо, выглядит действительно завершено сейчас.
мягкий
@ dav_i Это несправедливо сравнивать оба, так как оба ведут себя по-разному. ответ свика более полный.
nawfal
1
@dav_i Нет, они не выполняют ту же функцию. Ответ Мартина проверяет, существует ли свойство в обычном типе времени компиляции в C #, которое объявляется динамическим (то есть оно игнорирует проверки безопасности во время компиляции). Принимая во внимание, что ответ svick проверяет, существует ли свойство действительно динамического объекта, то есть того, что реализует IIDynamicMetaObjectProvider. Я понимаю мотивацию вашего ответа и ценю это. Справедливо ответить так.
nawfal
52

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

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
Мартейн
источник
2
Цитата из вопроса «Я мог бы сделать GetType (), но я бы предпочел избежать этого»
Roundcrisis
Разве это не имеет те же недостатки, что и мое предложение? RouteValueDictionary использует отражение для получения свойств .
Стив Уилкс
12
Вы можете просто обойтись без Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i
Пожалуйста, смотрите мой ответ для сравнения ответов.
Дав_и
3
Как однострочник ((Type)myVar.GetType()).GetProperties().Any(x => x.Name.Equals("PropertyName")). Приведение к типу требуется, чтобы компилятор радовался лямбде.
MushinNoShin
38

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

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

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

if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
    Console.WriteLine(test.foo);
}
karask
источник
3
Не уверен, почему этот ответ не имеет больше голосов, потому что он делает именно то, о чем просили (без исключений или размышлений).
Волчья голова
7
@Wolfshead Этот ответ хорош, если вы знаете, что ваш динамический объект - это ExpandoObject или что-то еще, что реализует IDictionary <string, object>, но если это что-то другое, то это не удастся.
Дамиан Пауэлл
9

Два распространенных решения этого включают в себя выполнение вызова и перехват 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;
    }
}
Дамиан Пауэлл
источник
Оказывается, Dynamiteyпакет nuget уже делает это. ( nuget.org/packages/Dynamitey )
Дамиан Пауэлл
8

Ответ Дениса заставил меня подумать о другом решении, используя 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;
Чарльз ГЕТЬЕР
источник
1
Есть ли шанс узнать, что не так с этим ответом, пожалуйста?
Чарльз Гетье
Не знаю, почему за это проголосовали, отлично сработало для меня. Я переместил Predicate для каждого свойства в вспомогательный класс и вызвал метод Invoke, чтобы вернуть bool из каждого свойства.
markp3rry
7

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

Используя 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 ()».

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

Диего Сантин
источник
Спасибо, очень полезно. Используя SharpTestsEx, я использую следующую строку, чтобы также проверить значение динамического свойства:((string)(testedObject.MyName)).Should().Be("I am a testing object");
Ремко Янсен
2

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

public static bool HasProperty(ExpandoObject expandoObj,
                               string name)
{
    return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}
Wolfshead
источник
2

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

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;
    }
}
шут
источник
nullне означает, что собственность не существует
Кетцалькоатль
Я знаю, но если оно равно нулю, мне не нужно ничего делать со значением, поэтому для моего сценария использования все в порядке
Jester
0

Если вы контролируете тип, используемый как динамический, не могли бы вы вернуть кортеж вместо значения для каждого доступа к свойству? Что-то вроде...

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

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

Возможно, это наивная реализация, но если вы каждый раз создаете один из них внутренне и возвращаете его вместо фактического значения, вы можете проверять Existsкаждый доступ к свойству, а затем нажимать, Valueесли это происходит со значением default(T)(и не имеет значения), если это не так.

Тем не менее, я мог бы упустить некоторые знания о том, как работает динамический, и это не может быть реальным предложением.

Shibumi
источник
0

В моем случае мне нужно было проверить существование метода с определенным именем, поэтому я использовал для этого интерфейс

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

Кроме того, интерфейсы могут содержать больше, чем просто методы:

Интерфейсы могут содержать методы, свойства, события, индексаторы или любую комбинацию этих четырех типов членов.

From: Interfaces (Руководство по программированию в C #)

Элегантный и не нужно ловить исключения или играть с отражением ...

Фред Маурой
источник
0

Я знаю, что это действительно старый пост, но здесь есть простое решение для работы с 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 );
    }
}
Симперт
источник
0

Как ExpandoObjectнаследует, IDictionary<string, object>вы можете использовать следующую проверку

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Вы можете сделать служебный метод для выполнения этой проверки, который сделает код намного чище и может использоваться повторно

Мукеш Бходжвани
источник
-1

Вот другой способ:

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;
            }
        }
    }
}
Денис
источник
2
Откуда вы взяли, что вопрос заключается в тестировании свойств JObject? Ваш ответ ограничен объектами / классами, которые предоставляют IEnumerable по своим свойствам. Не гарантируется dynamic. dynamicКлючевое слово гораздо шире. Пойди проверь, можешь ли ты проверить Countв dynamic foo = new List<int>{ 1,2,3,4 }таком виде
кетцалькоатль