Як визначити, чи існує властивість у ExpandoObject?


188

У javascript ви можете визначити, чи визначено властивість за допомогою ключового слова undefined:

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

Як би ви це зробили в C #, використовуючи динамічне ключове слово з ExpandoObjectі без викидання винятку?


5
@CodeInChaos: Зауважте, що відображений код не перевіряє значення data.myProperty; він перевіряє, що typeof data.myPropertyповертає. Це правильно, що data.myPropertyможе існувати і встановлюватись undefined, але в цьому випадку typeofповерне щось інше, ніж "undefined". Отже, цей код працює.
Асмунд Ельдхусет

Відповіді:


181

Згідно з MSDN, декларація показує, що він реалізує IDictionary:

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

Ви можете використовувати це, щоб побачити, чи визначений член:

var expandoObject = ...;
if(((IDictionary<String, object>)expandoObject).ContainsKey("SomeMember")) {
    // expandoObject.SomeMember exists.
}

3
Щоб зробити цю перевірку простішою, я перевантажив TryGetValue і змушу його завжди повертати істину, встановлюючи значення повернення на "невизначене", якщо властивість не було визначено. if (someObject.someParam! = "undefined") ... І це працює :)
Softlion

Можливо також :), але я думаю, ти мав на увазі "переохолоджений" замість перевантаженого.
Дікам

так. Я знову змінив "невизначене" на значення const спеціального об'єкта, створене в інших місцях. Це запобігає проблемам з литтям: p
Softlion

1
Я вважаю, що це рішення все ще є актуальним; не сприймайте нікого за ціну роздумів - протестуйте це на собі і подивіться, чи можете ви собі це дозволити
nik.shornikov

1
@ BlueRaja-DannyPflughoeft Так, це так. Без виступу це буде просто динамічне виклик, із випадком, який ви отримаєте у внутрішніх справах. Більш конкретно, це явно реалізовано: github.com/mono/mono/blob/master/mcs/class/dlr/Runtime/…
Dykam

28

Тут слід зробити важливе розмежування.

Більшість відповідей тут специфічні для ExpandoObject, який згадується у питанні. Але загальне використання (і причина, коли слід шукати це питання під час пошуку) - це під час використання ASP.Net MVC ViewBag. Це спеціальна реалізація / підклас DynamicObject, яка не кине виняток, коли ви перевіряєте будь-яке довільне ім'я властивості на null. Припустимо, ви можете оголосити властивість на зразок:

@{
    ViewBag.EnableThinger = true;
}

Тоді припустимо, що ви хотіли перевірити його значення та чи воно навіть встановлене - чи існує воно. Наступне дійсне, компілює, не викидає жодних винятків, і дає правильну відповідь:

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

Тепер позбудьтеся декларації EnableThinger. Той самий код компілюється та працює належним чином. Не потрібно роздумів.

На відміну від ViewBag, ExpandoObject буде кидати, якщо ви перевіряєте на null властивість, яка не існує. Для того, щоб вимкнути функціональність MVC ViewBag з ваших dynamicоб'єктів, вам потрібно буде використовувати динамічну реалізацію, яка не кидає.

Ви можете просто використовувати точну реалізацію в 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

Ви можете бачити, як це пов'язано з MVC Views тут, на MVC ViewPage:

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

Ключовим фактором витонченої поведінки DynamicViewDataDictionary є реалізація словника у ViewDataDictionary, тут:

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

Іншими словами, він завжди повертає значення для всіх ключів, незалежно від того, що в ньому знаходиться - він просто повертає нульове значення, коли нічого там немає. Але ViewDataDictionary зобов’язаний бути прив’язаним до моделі MVC, тому краще виключити лише витончені частини словника для використання поза MVC Views.

Занадто довго, щоб насправді розмістити тут усі кишки - більшість із них просто реалізує IDictionary - але ось динамічний об’єкт (клас DDict), який не кидає на нуль перевірки властивостей, які не були оголошені, на Github:

https://github.com/b9chris/GracefulDynamicDictionary

Якщо ви просто хочете додати його до свого проекту через NuGet, його ім'я - GracefulDynamicDictionary .


Чому ви проголосували проти DynamicDictionary, оскільки він не використовує рефлексію тоді?
Softlion

тоді ви можете проголосувати, оскільки це те саме рішення :)
Softlion

3
Це, звичайно, не те саме рішення.
Кріс Москіні

"Вам потрібно створити подібний підклас, який не викидається, коли властивість не знайдена." => це! О ні, це не так. Моє рішення краще. Він кидає - тому що ми цього хочемо, І він також не може кинути, якщо використовується TryXX;
Softlion

1
Це, саме тому я тут, я не міг зрозуміти, чому якийсь код (viewbag) НЕ порушився. Спасибі.
Адам Толлі

11

Нещодавно я відповів на дуже подібне запитання: як я розмірковую над членами динамічного об'єкта?

Незабаром, ExpandoObject - не єдиний динамічний об’єкт, який ви можете отримати. Відображення буде працювати для статичних типів (типи, які не реалізують IDynamicMetaObjectProvider). Для типів, які реалізують цей інтерфейс, відображення в основному марно. Для ExpandoObject ви можете просто перевірити, чи властивість визначено як ключ у нижньому словнику. Для інших реалізацій це може бути складним, а іноді єдиним способом є робота з винятками. Детальніше перейдіть за посиланням вище.


11

ОНОВЛЕНО: Ви можете використовувати делегатів і спробувати отримати значення з властивості динамічного об'єкта, якщо воно існує. Якщо властивості немає, просто зловіть виняток і поверніть хибне.

Погляньте, для мене це добре працює:

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

Вихід коду такий:

True
True
False

2
@marklam погано ловити всі винятки, коли ти не знаєш, що викликає виняток. У наших випадках це нормально, оскільки ми очікуємо можливої ​​відсутності поля.
Олександр Г

3
якщо ви знаєте, що викликає виняток, ви також повинні знати його тип, тому вловлюйте (WeverException) Інакше ваш код буде мовчки продовжуватися, навіть якщо ви отримали несподіваний виняток - наприклад OutOfMemoryException.
Марклам

4
Ви можете перейти в будь-який геттер до IsPropertyExist. У цьому прикладі ви знаєте, що можна кинути InvalidOperationException. На практиці ви поняття не маєте, який виняток може бути кинутий. +1 для протидії вантажному культу.
пієдар

2
Це рішення є неприйнятним, якщо продуктивність важлива, наприклад, якщо використовується в циклі з 500+ ітераціями, він додається і може спричинити затримку багато секунд. Кожен раз, коли
вибирається

1
Re: Продуктивність: відладчик додається і Console.WriteLine - це повільні біти. Тут 10 000 ітерацій займають менше 200 мс (за 2 винятками за ітерацію). Цей же тест, без винятку, займає кілька мілісекунд. Це означає, що якщо ви очікуєте, що при використанні цього коду рідко не вистачає ресурсу, або якщо ви називаєте його обмеженою кількістю разів, або ви можете кешувати результати, то, будь ласка, зрозумійте, що все має своє місце і жодне з надмірних -регулярні попередження тут мають значення.
Хаос

10

Я хотів створити метод розширення, щоб я міг зробити щось на кшталт:

dynamic myDynamicObject;
myDynamicObject.propertyName = "value";

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

... але ви не можете створювати розширення ExpandoObjectвідповідно до документації C # 5 (більше інформації тут ).

Тож я закінчив створити помічника класу:

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

Щоб використовувати його:

// If the 'MyProperty' property exists...
if (ExpandoObjectHelper.HasProperty(obj, "MyProperty"))
{
    ...
}

4
голосувати за корисний коментар та посилання на розширення для ExpandoObject.
Роберто

1

Чому ви не хочете використовувати Reflection для отримання набору типів проперій? Подобається це

 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
 } 

Я не впевнений, що це поверне динамічно додані властивості, я здогадуюсь, що він повертає методи об’єкта Dynamic.
Дікам

1

Цей метод розширення перевіряє наявність властивості, а потім повертає значення або null. Це корисно, якщо ви не хочете, щоб ваші програми викидали непотрібні винятки, принаймні ті, яким ви можете допомогти.

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

Якщо вони можуть бути використані як такі:

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

або якщо ваша локальна змінна "динамічна", вам доведеться спочатку передати її ExpandoObject.

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

1

Залежно від випадку використання, якщо null можна вважати таким же, як невизначений, ви можете перетворити ExpandoObject в 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"));

Вихід:

a is 1
b is 2.5
c is undefined


-3

Ей, хлопці, перестаньте використовувати Reflection для всього, що коштує багато циклів процесора.

Ось таке рішення:

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

4
Це показує, як реалізувати динамічний об'єкт, а не як бачити, як властивість виходить на динамічний об'єкт.
Метт Уоррен

Ви можете перевірити, чи має динамічний екземпляр властивість, зробивши нульову перевірку щодо відповідного властивості.
ctorx

2
"Це показує, як реалізувати динамічний об'єкт": так, насправді це так. Вирішення цього питання полягає в тому, що немає загального рішення, оскільки це залежить від реалізації.
Softlion

@Softlion Ні, рішення - це те, що нам доведеться припинити використовувати
nik.shornikov

@Softlion Який сенс методів Tryxxx? TryGet ніколи не поверне помилковий, якщо він не знайде властивість, тому вам все одно доведеться перевірити результат. Повернення марно. У TrySet, якщо ключ не існує, він викине виняток замість повернення false. Я не розумію, чому ви навіть використовуєте це як відповідь, якщо ви самі писали тут у коментарях "Рішення цього питання: немає загального рішення, оскільки це залежить від реалізації", це також не відповідає дійсності. Подивіться на відповідь Дікама щодо реального рішення.
pqsk

-5

Спробуйте це

public bool PropertyExist(object obj, string propertyName)
{
 return obj.GetType().GetProperty(propertyName) != null;
}

3
Це дозволило б перевірити наявність властивості об'єкта, прихованого під динамічним іменем, що є деталлю реалізації. Ви перевірили своє рішення в реальному коді перед публікацією? Це не повинно працювати взагалі.
Softlion

Я використовував цей фрагмент коду в режимі реального часу. Це добре працює.
Венкат

6
Дає мені нуль весь час, навіть якщо властивість існує.
atlantis

Він би працював з базовими об'єктами, але не з ExpandoObjects. Динаміка, не впевнений.
Джо

1
Для підтвердження це також не працює з dynamicоб’єктами (завжди повертається null).
Пройшов кодування
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.