Проверьте, доступно ли свойство для динамической переменной
Моя ситуация очень проста. Где-то в моем коде у меня есть это:
dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
//How to do this?
if (myVariable.MyProperty.Exists)
//Do stuff
Итак, в основном мой вопрос заключается в том, как проверить (без исключения), что определенное свойство доступно для моей динамической переменной. я мог бы сделать GetType()
но я бы предпочел этого избежать, так как мне не нужно знать тип объекта. Все, что я действительно хочу знать, это то, доступно ли свойство (или метод, если это облегчает жизнь). Есть указатели?
15 ответов
Я думаю, что нет никакого способа узнать, является ли dynamic
переменная имеет определенный член, не пытаясь получить к нему доступ, если только вы не реализовали способ, которым динамическое связывание обрабатывается в компиляторе C#. Который, вероятно, будет включать много догадок, потому что это определяется реализацией в соответствии со спецификацией C#.
So you should actually try to access the member and catch an exception, if it fails:
dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
try
{
var x = myVariable.MyProperty;
// do stuff with x
}
catch (RuntimeBinderException)
{
// MyProperty doesn't exist
}
Я думал, что сделаю сравнение ответа Мартина и ответа Свика...
Следующая программа возвращает следующие результаты:
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()
для каждого типа в словаре или подобном. Это может помочь, если вы снова и снова проверяете один и тот же набор типов.
Может быть, использовать отражение?
dynamic myVar = GetDataThatLooksVerySimilarButNotTheSame();
Type typeOfDynamic = myVar.GetType();
bool exist = typeOfDynamic.GetProperties().Where(p => p.Name.Equals("PropertyName")).Any();
На всякий случай это кому-то поможет:
Если метод GetDataThatLooksVerySimilarButNotTheSame()
возвращает ExpandoObject
Вы также можете привести к IDictionary
перед проверкой.
dynamic test = new System.Dynamic.ExpandoObject();
test.foo = "bar";
if (((IDictionary<string, object>)test).ContainsKey("foo"))
{
Console.WriteLine(test.foo);
}
Два общих решения для этого включают в себя вызов и перехват 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;
}
}
Ответ Дениса заставил меня задуматься над другим решением, использующим 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;
Ну, я столкнулся с подобной проблемой, но на модульных тестах.
Используя 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();
Используя это, я тестирую все существующие свойства, используя "Должен ().NotThrow()".
Это, вероятно, не по теме, но может быть полезно для кого-то.
Для меня это работает:
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;
}
}
Исходя из ответа @karask, вы можете обернуть функцию как помощник так:
public static bool HasProperty(ExpandoObject expandoObj,
string name)
{
return ((IDictionary<string, object>)expandoObj).ContainsKey(name);
}
Если вы контролируете тип, используемый как динамический, не могли бы вы вернуть кортеж вместо значения для каждого доступа к свойству? Что-то вроде...
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)
(и не имеет значения), если это не так.
Тем не менее, я мог бы упустить некоторые знания о том, как работает динамический, и это не может быть реальным предложением.
Если ваш вариант использования заключается в преобразовании ответа API, содержащего всего несколько полей, вы можете использовать это:
var template = new { address = new { street = "" } };
var response = JsonConvert.DeserializeAnonymousType(await result.Content.ReadAsStringAsync(), template);
string street = response?.address?.street;
Вот другой способ:
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;
}
}
}
}
Я знаю, что это действительно старый пост, но вот простое решение для работы с dynamic
введите c#
,
- можно использовать простое отражение для перечисления прямых свойств
- или можете использовать
object
метод расширения- или использовать
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 );
}
}
Как ExpandoObject
наследует IDictionary<string, object>
вы можете использовать следующую проверку
dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();
if (((IDictionary<string, object>)myVariable).ContainsKey("MyProperty"))
//Do stuff
Вы можете создать служебный метод для выполнения этой проверки, который сделает код намного чище и пригодным для повторного использования.
В моем случае мне нужно было проверить существование метода с определенным именем, поэтому я использовал для этого интерфейс
var plugin = this.pluginFinder.GetPluginIfInstalled<IPlugin>(pluginName) as dynamic;
if (plugin != null && plugin is ICustomPluginAction)
{
plugin.CustomPluginAction(action);
}
Кроме того, интерфейсы могут содержать больше, чем просто методы:
Интерфейсы могут содержать методы, свойства, события, индексаторы или любую комбинацию этих четырех типов членов.
From: Interfaces (Руководство по программированию в C#)
Элегантный и не нужно ловить исключения или играть с отражением...