Перевірте, чи є властивість доступною в динамічній змінній


225

Моя ситуація дуже проста. Десь у коді я маю таке:

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Отже, в основному моє питання полягає в тому, як перевірити (не кидаючи виняток), що певна властивість є в моїй динамічній змінній. Я міг би це зробити, GetType()але я вважаю за краще уникати цього, оскільки мені не потрібно знати тип об'єкта. Мені дуже хочеться знати, чи є властивість (або метод, якщо це полегшує життя). Якісь покажчики?


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
} 

2
Я відзначу це як відповідь так довго, але, здається, це найкраща відповідь
кругокриз

8
Краще рішення - stackoverflow.com/questions/2839598/…
ministmason

20
@ministrymason Якщо ви маєте на увазі кастинг IDictionaryта роботу з цим, що працює лише на ExpandoObject, він не працюватиме на будь-якому іншому dynamicоб’єкті.
svick

5
RuntimeBinderExceptionзнаходиться в Microsoft.CSharp.RuntimeBinderпросторі імен.
DavidRR

8
Я все ще відчуваю, що використовувати пробувати / ловити замість того, якщо / ще є поганою практикою взагалі незалежно від специфіки цього сценарію.
Олександр Райан Багетт

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

Як результат, я б запропонував використовувати рефлексію. Дивись нижче.


Відповідаючи на коментар bland:

Коефіцієнти - це 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()для кожного типу у словнику чи подібному. Це може допомогти, якщо ви знову і знову перевіряєте один і той же набір типів.


7
Я згоден і люблю рефлексію в своїй роботі, де це доречно. Виграш від Try / Catch є виключно тоді, коли виняток викидається. Отже, що хтось повинен запитати, перш ніж використовувати тут рефлексію - це, певно, певний шлях? 90% чи навіть 75% часу, чи пройде ваш код? Тоді Try / Catch все ще є оптимальним. Якщо його в повітрі або занадто багато варіантів для одного, швидше за все, то ваше відображення не вдається.
мягкий

@bland Відредагована відповідь.
dav_i

1
Дякую, зараз виглядає дійсно повно.
блан

@dav_i не справедливо порівнювати обидва, оскільки обидва поводяться по-різному. Відповідь svick є більш повною.
nawfal

1
@dav_i Ні, вони не виконують однакову функцію. Відповідь Martijn перевіряє, чи існує властивість у звичайному типі часу компіляції у 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 (), але я вважаю за краще цього уникати"
круглий кризис

Хіба це не має тих самих недоліків, що і моя пропозиція? RouteValueDictionary використовує відображення для отримання властивостей .
Стів Вілкс

12
Ви можете просто обійтися без Where:.Any(p => p.Name.Equals("PropertyName"))
dav_i

Будь ласка, дивіться мою відповідь для порівняння відповідей.
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);
}

3
Не впевнений, чому ця відповідь не має більшої кількості голосів, адже вона робить саме те, про що вимагали (не виняток кидок чи роздум).
Wolfshead

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.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
Чи є можливість дізнатися, що не так з цією відповіддю?
Чарльз ХЕТЬЕР

Не знаю, чому за це було оголошено голосування, мені добре підійшло. Я перемістив предикат для кожної властивості в клас помічників і закликав метод 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);
}

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не означає, що властивість не існує
quetzalcoatl

Я знаю, але якщо це недійсно, мені не потрібно нічого робити зі значенням, тому для моєї корисної справи це нормально
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)(і не має значення), якщо це не так.

Однак, можливо, мені не вистачає знань про те, як працює динаміка, і це не може бути корисною пропозицією.


0

У моєму випадку мені потрібно було перевірити наявність методу із конкретною назвою, тому я використовував для цього інтерфейс

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

Також інтерфейси можуть містити більше, ніж просто методи:

Інтерфейси можуть містити методи, властивості, події, індексатори або будь-яку комбінацію цих чотирьох типів членів.

Від: Інтерфейси (Посібник з програмування 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? Ваша відповідь обмежена об'єктами / класами, які розкривають IEbrobro над їх властивостями. Чи не гарантується dynamic. dynamicКлючове слово набагато ширша тема. Перевірка Go , якщо ви можете перевірити Countв dynamic foo = new List<int>{ 1,2,3,4 }як то
Кетцалькоатля
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.