Проверить, доступно ли свойство для динамической переменной

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

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

12 ответов


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

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

dynamic myVariable = GetDataThatLooksVerySimilarButNotTheSame();

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

Я думал, что сделаю сравнение Мартейн по и svick это!--8-->...

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

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

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

С помощью SharpTestsEx вы можете проверить, если свойство existis. Я использую это тестирование своих контроллеров, потому что, поскольку объект 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 ()".

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


два общих решения для этого включают в себя вызов и ловлю 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;

для меня это работает:

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

тем не менее, я, возможно, отсутствует некоторые знания о том, как работает динамика, и это может быть неосуществимым предложением.


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

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

кроме того, интерфейсы могут содержать не только методы:

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

From:Интерфейсы (Руководство По Программированию На C#)

элегантный и не нужно ловить исключения или играть с отражение...


вот так:

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