Есть ли способ выполнить цепочку нулевой проверки в динамическом/expando?

С# имеет полезный Null Conditional Operator. Хорошо объяснил в этот ответ тоже.

Мне было интересно, можно ли сделать подобную проверку, например, когда мой объект является объектом dynamic/expando. Позвольте мне показать вам код:

Учитывая эту иерархию классов

public class ClsLevel1
{
    public ClsLevel2 ClsLevel2 { get; set; }
    public ClsLevel1()
    {
        this.ClsLevel2 = new ClsLevel2(); // You can comment this line to test
    }        
}

public class ClsLevel2
{
    public ClsLevel3 ClsLevel3 { get; set; }
    public ClsLevel2()
    {
        this.ClsLevel3 = new ClsLevel3();
    }       
}

public class ClsLevel3
{
    // No child
    public ClsLevel3()
    {
    }
}

Если я выполняю нулевой нулевой чек, он работает

ClsLevel1 levelRoot = new ClsLevel1();
if (levelRoot?.ClsLevel2?.ClsLevel3 != null)
{
     // will enter here if you DO NOT comment the content of the ClsLevel1 constructor
}
else
{
     // will enter here if you COMMENT the content of the ClsLevel1 
}

Теперь я попытаюсь воспроизвести это поведение с динамикой (ExpandoObjects)

dynamic dinRoot = new ExpandoObject();
dynamic DinLevel1 = new ExpandoObject();
dynamic DinLevel2 = new ExpandoObject();
dynamic DinLevel3 = new ExpandoObject();

dinRoot.DinLevel1 = DinLevel1;
dinRoot.DinLevel1.DinLevel2 = DinLevel2;
//dinRoot.DinLevel1.DinLevel2.DinLevel3 = DinLevel3; // You can comment this line to test

if (dinRoot?.DinLevel1?.DinLevel2?.DinLevel3 != null)
{
     // Obviously it will raise an exception because the DinLevel3 does not exists, it is commented right now.
}

Есть ли способ имитировать это поведение с динамикой? Я имею в виду, проверить нуль в длинной цепочке членов?

Ответ 1

Если вы хотите поддержать это более естественным образом, вы можете наследовать от DynamicObject и предоставить собственную реализацию:

class MyExpando : DynamicObject
    {
        private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            var name = binder.Name.ToLower();
            result = _dictionary.ContainsKey(name) ? _dictionary[name] : null;
            return true;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            _dictionary[binder.Name.ToLower()] = value;
            return true;
        }
    }

Тестирование:

 private static void Main(string[] args)
        {
            dynamic foo = new MyExpando();
            if (foo.Boo?.Lol ?? true)
            {
                Console.WriteLine("It works!");
            }
            Console.ReadLine();
        }

Выход будет "Он работает!". Поскольку Boo не существует, мы получаем нулевую ссылку, чтобы Null Conditional Operator мог работать.

Здесь мы должны вернуть ссылку null на выходной параметр TryGetMember каждый раз, когда свойство не найдено, и мы всегда возвращаем true.

Надеюсь, что это поможет

Ответ 2

EDIT: исправлено, так как ExpandoObjects и методы расширения не работают вместе. Чуть менее приятно, но, надеюсь, все еще можно использовать.

Вспомогательный метод (ы):

public static class DynamicExtensions
{
    public static Object TryGetProperty(ExpandoObject obj, String name)
    {
        return name.Split('.')
                   .Aggregate((Object)obj, (o, s) => o != null
                                                      ? TryGetPropertyInternal(o, s)
                                                      : null);
    }

    private static Object TryGetPropertyInternal(Object obj, String name)
    {
        var dict = obj as IDictionary<String, Object>;
        return (dict?.ContainsKey(name) ?? false) ? dict[name] : null;
    }
}

Использование:

if (DynamicExtensions.TryGetProperty(dinRoot, "DinLevel1.DinLevel2.DinLevel3") != null)