Как определить, существует ли свойство в ExpandoObject?


188

В javascript вы можете определить, определено ли свойство с помощью неопределенного ключевого слова:

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

Как бы вы сделали это в C #, используя динамическое ключевое слово с ExpandoObjectи без исключения?


5
@CodeInChaos: обратите внимание, что отображаемый код не проверяет значение data.myProperty; он проверяет, что typeof data.myPropertyвозвращается. Это верно, что data.myPropertyможет существовать и быть установленным undefined, но в этом случае typeofбудет возвращать что-то отличное от "undefined". Так что этот код работает.
Aasmund Eldhuset

Ответы:


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 и всегда возвращаю true, устанавливая возвращаемое значение «undefined», если свойство не было определено. if (someObject.someParam! = "undefined") ... И это работает :)
Softlion

Также возможно :), но я думаю, что вы имели в виду "переопределенный" вместо перегруженного.
Dykam

Ага. Я снова изменил «undefined» на константное значение специального объекта, которое я создал в другом месте. Это предотвращает проблемы с кастингом: 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

ОБНОВЛЕНО: Вы можете использовать делегаты и пытаться получить значение из свойства динамического объекта, если оно существует. Если свойства нет, просто перехватите исключение и верните false.

Посмотрите, у меня все работает нормально:

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
если вы знаете, что вызывает исключение, вы также должны знать его тип, так что перехватывайте (WheverException). В противном случае ваш код продолжит молчание, даже если вы получили непредвиденное исключение - например, OutOfMemoryException.
Марклам

4
Вы можете перейти в любой метод получения IsPropertyExist. В этом примере вы знаете, что можно бросить InvalidOperationException. На практике вы не представляете, какое исключение может быть выдано. +1, чтобы противодействовать культу груза.
Пьедар

2
Это решение неприемлемо, если важна производительность, например, если оно используется в цикле с 500+ итерациями, оно складывается и может вызвать много секунд задержки. Каждый раз, когда обнаруживается исключение, стек должен быть скопирован в объект исключения
Jim109

1
Re: Производительность: присоединяемый отладчик и Console.WriteLine - медленные биты. 10000 итераций здесь занимает менее 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
 } 

Я не уверен, будет ли это возвращать динамически добавленные свойства, я предполагаю, что он возвращает методы динамического объекта.
Dykam

1

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

    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 может рассматриваться как совпадающий с undefined, вы можете превратить ваш 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 10.06.13

@Softlion В чем смысл методов Tryxxx? TryGet никогда не вернет false, когда не найдет свойство, поэтому вам все равно придется проверить результат. Возвращение бесполезно. В TrySet, если ключ не существует, он выдаст исключение вместо возврата false. Я не понимаю, почему вы бы даже использовали это в качестве ответа, если бы вы сами написали здесь в комментариях «Решение этого вопроса: нет общего решения, поскольку оно зависит от реализации», это тоже не так. Посмотрите на ответ Dykam для реального решения.
pqsk

-5

Попробуй это

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

3
Это проверило бы существование свойства объекта, скрытого под динамическим именем, которое является деталью реализации. Вы проверили свое решение в реальном коде перед публикацией? Это не должно работать вообще.
Softlion

Я использовал этот кусок кода в режиме реального времени. Работает хорошо.
Венкат

6
Дает мне ноль все время, даже если свойство существует.
Атлантида

Это будет работать с базовыми объектами, но не с ExpandoObjects. Динамика, не уверен.
Джо

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