Is there a way to perform a chained null check in a dynamic/expando?
Asked Answered
S

2

3

C# has the usefull Null Conditional Operator. Well explained in this answer too.

I was wondering if it is possible to do a similar check like this when my object is a dynamic/expando object. Let me show you some code:

Given this class hierarchy

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()
    {
    }
}

If i perform this kind of chained null check, it works

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 
}

Now, i will try to reproduce this behaviour with dynamics (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.
}

Is there a way to simulate this behaviour with dynamics? I mean, check for a null in a long chain of members?

Sidwell answered 17/10, 2017 at 12:55 Comment(4)
So you want a does this property exist check rather than a null check?Mohawk
As far as I know - there is no way to do this.Vala
If there is no way, then we must build it.Rocky
@Mohawk in my case does this property exists and null check is the same thingSidwell
R
5

If you want to support this in a more natural way you can inherit from DynamicObject and provide a custom implementation:

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

Testing:

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

The output will be "It works!". Since Boo does not exist we get a null reference so that the Null Conditional Operator can work.

What we do here is to return a null reference to the output parameter of TryGetMember every time a property is not found and we always return true.

Rocky answered 17/10, 2017 at 13:45 Comment(3)
Thanks @taquion, i did some tests, like: 1) make the last expando null or not null, 2) make the last object a property null or not null, 3) make the "middle-expando" null or not null, so far, everything worked fineSidwell
Glad to know it! \m/ \m/Rocky
Is there a way to make the MyExpando acts like a default dynamic, i mean: dynamic d = new ExpandoObject(); d.SomeProp = "foo"; //works CustomDynamic c = new CustomDynamic(); c.SomeProp = "bar"; //Not worksSidwell
A
-1

EDIT: fixed, as ExpandoObjects and extension methods do not work well together. Slightly less nice, but hopefully still usable.

Helper method(s):

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

Usage:

if (DynamicExtensions.TryGetProperty(dinRoot, "DinLevel1.DinLevel2.DinLevel3") != null)
Anesthetic answered 17/10, 2017 at 13:10 Comment(3)
Did you test this? I don't think the chaining will work.Lancer
In an environment not supporting extension methods; somehow though it would work. Updating my answer now.Anesthetic
Extension methods and dynamics don't mix very well. Also, the "Propertyname" strings are an eyesore.Lancer

© 2022 - 2024 — McMap. All rights reserved.