2010-06-08 3 views
172

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame(); 

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

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

+1

Там не несколько предложений здесь: http://stackoverflow.com/questions/2985161/duck-type-testing-with-c-4-for-dynamic-objects - но не принял ответ до сих пор , –

+0

спасибо, я могу видеть, как сделать fir одним из решений, tho мне было интересно, есть ли что-нибудь, что я упускаю – roundcrisis

+0

Возможный дубликат [Как определить, существует ли свойство в ExpandoObject?] (Http: // stackoverflow .com/questions/2839598/how-to-detect-if-a-property-exists-on-an-expandoobject) – Sebastian

ответ

126

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

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame(); 

try 
{ 
    var x = myVariable.MyProperty; 
    // do stuff with x 
} 
catch (RuntimeBinderException) 
{ 
    // MyProperty doesn't exist 
} 
+1

Это лучший вариант – Randolpho

+2

Я отмечу это как ответ, поскольку он так долго, он кажется быть лучшим ответом – roundcrisis

+6

Лучшее решение - http://stackoverflow.com/questions/2839598/how-to-detect-if-a-property-exists-on-a-dynamic-object-in-c – ministrymason

42

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

dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame(); 
Type typeOfDynamic = myVar.GetType(); 
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any(); 
+2

Цитата из вопроса «Я мог бы делать GetType(), но я бы предпочел избежать этого» – roundcrisis

+0

Разве это не те же недостатки, что и мое предложение? [RouteValueDictionary использует отражение для получения свойств] (http://pastebin.com/c1gQpBMG). –

+10

Вы можете просто обойтись без 'Where':' .Any (p => p.Name.Equals ("PropertyName")) ' –

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) (и не имеет значения), если это не так.

Это говорит о том, что мне не хватает некоторых знаний о том, как динамично работает, и это может быть неправдоподобное предложение.

7

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

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

Пример:

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

Теперь, используя SharTestsEx: "В случае, если() NotThrow()"

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

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

Возможно, это из-за темы, но может быть полезно для кого-то.

+0

Спасибо, очень полезно. Используя SharpTestsEx, я использую следующую строку, чтобы также проверить значение динамического свойства: '((string) (testedObject.MyName)). Должен(). Be (« Я - объект тестирования »);' –

56

Я думал, что сделаю сравнение Martijn's answer и svick's answer ...

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

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

As a result I'd suggest using reflection. См. Ниже


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

+5

Я согласен и люблю отражения в моей работе, когда это необходимо. Прибыль, полученная в результате Try/Catch, возникает только при исключении исключения. Итак, что кто-то должен спросить, прежде чем использовать рефлексию здесь - возможно, это будет определенным образом? 90% или даже 75% времени, будет ли ваш код проходить? Тогда Try/Catch по-прежнему оптимален. Если он поднят в воздухе или слишком много вариантов для того, чтобы быть наиболее вероятным, тогда ваше отражение будет на месте. – bland

+0

@bland Отредактированный ответ. –

+1

Спасибо, сейчас выглядит отлично. – bland

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) ? false : true; 
    } 
    catch (RuntimeBinderException) 
    { 
     return false; 
    } 
    catch 
    { 
     return true; 
    } 
} 
+0

'null' не означает, что свойство не существует – quetzalcoatl

+0

Я знаю, но если это null, мне не нужно ничего делать со значением, поэтому для моего usecase это нормально – Jester

+0

Это работает, но вы в конце концов будете читать свойство дважды , –

-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' - это ** много ** более широкий предмет. Go проверить, можете ли вы проверить 'Count' в' dynamic foo = new List {1,2,3,4} 'like – quetzalcoatl

20

Только в случае, если это помогает кому-то:

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

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

if (((IDictionary<string, object>)test).ContainsKey("foo")) 
{ 
    Console.WriteLine(test.foo); 
} 
+2

Не знаете, почему в этом ответе больше нет голосов, потому что он делает именно то, что было задано для (исключение или отражение исключений). – Wolfshead

+2

@ Wolfshead Этот ответ велик, если вы знаете, что ваш динамический объект - это ExpandoObject или что-то еще, что реализует IDictionary , но если это будет что-то другое, тогда это не удастся. –

4

ответ Дениса заставил меня думать, к другому решению, используя 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

Есть ли шанс узнать, что не так с этим ответом, пожалуйста? –

+0

Не знаю, почему это проголосовало, отлично поработало для меня. Я переместил Predicate для каждого свойства в класс-помощник и вызвал метод Invoke, чтобы вернуть bool из каждого. – markp3rry

1

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

public static bool HasProperty(ExpandoObject expandoObj, 
           string name) 
{ 
    return ((IDictionary<string, object>)expandoObj).ContainsKey(name); 
} 
5

Два общих решения этого включают делает вызов и поймать 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; 
    } 
} 
+0

Оказывается, пакет 'Dynamitey' nuget уже делает это. (https://www.nuget.org/packages/Dynamitey/) –

+1

иногда вы не хотите, чтобы целый пакет делал то, что делают несколько строк ... – JJS

Смежные вопросы